canvod.grids API Reference¶
Hemispheric grid implementations and spatial analysis tools.
Package¶
HEALPix and hemispheric grid operations.
Provides hemisphere grid structures for GNSS signal observation analysis.
BaseGridBuilder
¶
Bases: ABC
Abstract base for hemispherical grid builders.
Parameters¶
angular_resolution : float Angular resolution in degrees cutoff_theta : float Maximum polar angle cutoff in degrees phi_rotation : float Rotation angle in degrees (applied to all phi values)
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
__init__(angular_resolution=2, cutoff_theta=0, phi_rotation=0)
¶
Initialize the grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
get_grid_type()
abstractmethod
¶
Get grid type identifier.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
84 85 86 | |
build()
¶
Build hemisphere grid.
Returns¶
GridData Complete grid data structure
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
GridData
dataclass
¶
Immutable container for hemispherical grid structure.
Parameters¶
grid : pl.DataFrame Grid cells with phi, theta, and bounds theta_lims : np.ndarray Theta band limits phi_lims : list[np.ndarray] Phi limits per theta band cell_ids : list[np.ndarray] Cell IDs per theta band grid_type : str Grid type identifier solid_angles : np.ndarray, optional Solid angles per cell [steradians] metadata : dict, optional Additional grid metadata voronoi : Any, optional Voronoi tessellation object (for Fibonacci grids) vertices : np.ndarray, optional 3D vertices (for triangular grids) points_xyz : np.ndarray, optional 3D point cloud (for Fibonacci grids) vertex_phi : np.ndarray, optional Vertex phi coordinates vertex_theta : np.ndarray, optional Vertex theta coordinates
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
coords
property
¶
Get cell coordinates.
ncells
property
¶
Number of cells in grid.
get_patches()
¶
Create matplotlib patches for polar visualization.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
67 68 69 70 71 72 73 74 75 76 77 78 | |
get_solid_angles()
¶
Calculate solid angle for each cell [steradians].
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
get_grid_stats()
¶
Get grid statistics including solid angle uniformity.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
GridType
¶
Bases: Enum
Available grid projection types for hemispherical tessellation.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_types.py
6 7 8 9 10 11 12 13 14 15 | |
EqualAreaBuilder
¶
Bases: BaseGridBuilder
Equal solid angle tessellation using concentric theta bands.
The hemisphere is divided into annular bands of constant width in theta. Within each band the number of azimuthal (phi) sectors is chosen so that every cell subtends approximately the same solid angle. This is the only grid type that has been validated for scientific use in this codebase.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle measured from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means¶
angular_resolution (degrees) sets the width of each theta band.
All bands have this same width Δθ. The azimuthal width of cells varies
by band: near the zenith cells are wide in phi; near the horizon they are
narrow, so that the solid angle stays constant.
Mathematical construction¶
-
Target solid angle per cell is chosen equal to the solid angle of a cap of half-angle Δθ/2::
Ω_target = 2π (1 − cos(Δθ/2))
-
Zenith cap – a single cell covers [0, Δθ/2] in theta and the full azimuth [0, 2π).
-
Theta bands – edges are placed at Δθ/2, 3Δθ/2, 5Δθ/2, … up to π/2 − cutoff_theta. For each band [θ_inner, θ_outer] the band's total solid angle is::
Ω_band = 2π (cos θ_inner − cos θ_outer)
-
Phi divisions – the number of sectors in the band is::
n_phi = round(Ω_band / Ω_target)
Each sector spans Δφ = 2π / n_phi. The cell centre is placed at the geometric midpoint of its (phi, theta) rectangle.
Parameters¶
angular_resolution : float Theta-band width in degrees. Controls both the radial resolution and (indirectly, via the equal-area constraint) the azimuthal resolution. cutoff_theta : float Minimum elevation above the horizon in degrees. Bands whose outer edge is at or below this cutoff are omitted. In GNSS terms this is the satellite elevation mask angle. phi_rotation : float Rigid rotation applied to all phi coordinates after grid construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_area_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equal_area"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_area_grid.py
68 69 70 71 72 73 74 75 76 77 | |
EqualAngleBuilder
¶
Bases: BaseGridBuilder
Equal angular spacing in both theta and phi (NOT equal area).
Every cell is a rectangle of the same angular size Δθ × Δφ in the
(theta, phi) parameter space. Because solid angle depends on cos(theta),
cells near the zenith subtend more solid angle than cells near the
horizon. This makes the grid biased toward the zenith for any
solid-angle-weighted statistic. Not recommended for scientific
analysis – use EqualAreaBuilder instead.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith
What angular_resolution means¶
angular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The number of phi divisions is constant across
all bands::
n_phi = round(2π / Δθ)
and does not change with latitude.
Mathematical construction¶
- A zenith cap cell covers [0, Δθ/2] × [0, 2π).
- Theta band edges are placed at Δθ/2, 3Δθ/2, … up to π/2.
- Within every band, the full azimuth is split into
n_phisectors of equal width Δφ = 2π / n_phi. - Cell centres are at the midpoint of each (phi, theta) rectangle.
Parameters¶
angular_resolution : float Angular spacing in degrees, applied identically in both theta and phi. cutoff_theta : float Elevation mask angle in degrees (bands below this are omitted). phi_rotation : float Rigid azimuthal rotation applied after construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_angle_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equal_angle"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_angle_grid.py
53 54 55 56 57 58 59 60 61 62 | |
EquirectangularBuilder
¶
Bases: BaseGridBuilder
Simple rectangular grid in (theta, phi) space.
The hemisphere is divided into a regular rectangular array: a constant
number of theta bands, each containing the same constant number of phi
sectors. Every cell is an identical rectangle in angular coordinates.
This is structurally identical to EqualAngleBuilder except for one
difference in the zenith treatment: EqualAngleBuilder collapses the
first band into a single zenith cap, while this builder does not — every
band has the same number of sectors.
Because solid angle depends on cos(theta), cells near the zenith subtend
more solid angle than cells near the horizon. This makes the grid
biased toward the zenith for any solid-angle-weighted statistic.
Not recommended for scientific analysis – use EqualAreaBuilder
instead.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means¶
angular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The grid is therefore square in angular
coordinates::
n_theta = round((π/2 − cutoff) / Δθ)
n_phi = round(2π / Δθ)
total cells = n_theta × n_phi
Mathematical construction¶
- Theta edges are placed at
cutoff_theta,cutoff_theta + Δθ,cutoff_theta + 2Δθ, … up to π/2. - Phi edges are placed at 0, Δθ, 2Δθ, … up to 2π.
- Every (theta_band, phi_sector) combination produces one cell. The cell centre is the midpoint of the rectangle.
- No special zenith cap is created; the band nearest the zenith has the same number of phi sectors as all other bands.
Parameters¶
angular_resolution : float
Angular spacing in degrees, applied identically in both theta and phi.
cutoff_theta : float
Elevation mask angle in degrees. Bands whose inner edge is at or
below π/2 − cutoff_theta are omitted.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equirectangular_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equirectangular"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equirectangular_grid.py
64 65 66 67 68 69 70 71 72 73 | |
HEALPixBuilder
¶
Bases: BaseGridBuilder
HEALPix tessellation (Hierarchical Equal Area isoLatitude Pixelization).
HEALPix partitions the sphere into 12 base pixels arranged at equal
latitudes. Each base pixel is recursively subdivided into 4 children,
producing 12 × nside² pixels on the full sphere, all with exactly
the same solid angle. This strict equal-area property makes HEALPix
the gold standard for pixelisations that must be unbiased under
solid-angle weighting.
This builder delegates the pixel geometry entirely to the healpy
library. It filters the full-sphere pixelisation down to the northern
hemisphere and stores approximate bounding boxes (phi_min/max,
theta_min/max) derived from the pixel resolution. The bounding
boxes are not the true pixel boundaries (which are curvilinear);
they are only approximations suitable for quick spatial queries. For
exact pixel membership use healpy.ang2pix directly.
Coordinate convention¶
HEALPix natively uses colatitude theta ∈ [0, π] (0 = North Pole)
and longitude phi ∈ [0, 2π). This matches the GNSS convention used
elsewhere in canvodpy: theta = 0 is the zenith, theta = π/2 is the
horizon. No coordinate transform is applied.
What nside (resolution) means¶
nside is the single resolution parameter of HEALPix. It must be a
power of 2. The key derived quantities are::
n_pixels = 12 × nside² (full sphere)
pixel_area = 4π / n_pixels (steradians, exact)
resolution ≈ √(pixel_area) (approximate angular diameter)
≈ 58.6° / nside (degrees)
| nside | Pixels (full) | Approx resolution | Pixel area (sr) |
|---|---|---|---|
| 1 | 12 | 58.6° | 1.049 |
| 2 | 48 | 29.3° | 0.262 |
| 4 | 192 | 14.7° | 0.065 |
| 8 | 768 | 7.3° | 0.016 |
| 16 | 3 072 | 3.7° | 0.004 |
| 32 | 12 288 | 1.8° | 0.001 |
When nside is not provided, it is estimated from angular_resolution
and rounded to the nearest power of 2::
nside_estimate = round_to_pow2( √(3/π) × 60 / angular_resolution )
Mathematical construction¶
HEALPix construction is performed entirely by healpy. At a high
level:
- The sphere is divided into 12 congruent base pixels (a curvilinear quadrilateral arrangement at three latitude zones: polar caps and equatorial belt).
- Each base pixel is subdivided into
nside²equal-area children using a hierarchical quadtree. - Pixel centres are returned by
healpy.pix2ang(nside, ipix)in RING ordering (pixels ordered by increasing colatitude). - This builder keeps only pixels with
theta ≤ π/2 − cutoff_theta(northern hemisphere above the elevation mask).
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
nside when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Pixels with colatitude
theta > π/2 − cutoff_theta (i.e. below the mask) are excluded.
nside : int or None
HEALPix resolution parameter. Must be a power of 2. If None,
estimated from angular_resolution.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Raises¶
ImportError
If healpy is not installed.
ValueError
If nside is not a power of 2.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
__init__(angular_resolution=2, cutoff_theta=0, nside=None, phi_rotation=0)
¶
Initialize the HEALPix grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. nside : int | None, optional HEALPix nside parameter. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"healpix"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
147 148 149 150 151 152 153 154 155 156 | |
get_healpix_info()
¶
Get HEALPix-specific information.
Returns¶
info : dict
Keys: nside, npix_total, pixel_area_sr,
pixel_area_arcmin2, resolution_arcmin,
resolution_deg, max_pixel_radius_deg.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
GeodesicBuilder
¶
Bases: BaseGridBuilder
Geodesic grid based on a subdivided icosahedron.
The sphere is tessellated into triangular cells by starting with an icosahedron (20 equilateral triangles) and recursively subdividing each triangle into four smaller triangles. All vertices are projected back onto the unit sphere after each subdivision step, so the final cells are spherical triangles. The grid has no polar singularity and provides near-uniform cell areas, though strict equal-area is not guaranteed — cell areas vary by a few percent depending on how they inherit the icosahedral symmetry axes.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centres are computed as the 3D Cartesian mean of the three vertices, re-normalised onto the unit sphere.
What angular_resolution means¶
angular_resolution is not used directly as a cell size. Instead it
is used only when subdivision_level is not explicitly supplied, to
estimate an appropriate subdivision level. The heuristic targets an
approximate triangle edge length of 2 × angular_resolution::
target_edge ≈ 2 × angular_resolution (degrees)
subdivision_level = ceil(log₂(63.4 / target_edge))
The number 63.4° is the edge length of a regular icosahedron inscribed in a unit sphere. Each subdivision halves the edge length, so the actual edge length at level n is approximately::
edge ≈ 63.4° / 2ⁿ (degrees)
The total number of triangles on the full sphere is 20 × 4ⁿ.
Roughly half fall in the northern hemisphere (exact count depends on
the hemisphere boundary).
Mathematical construction¶
- Icosahedron – 12 vertices placed at the intersections of three mutually perpendicular golden-ratio rectangles, normalised to the unit sphere. 20 triangular faces connect them.
- Subdivision – each triangle is split into 4 by inserting edge
midpoints. Each midpoint is projected onto the unit sphere
(re-normalised) before the next subdivision. This is repeated
subdivision_leveltimes. - Hemisphere filter – faces are kept if any of their three
vertices satisfies
theta ≤ π/2 − cutoff_theta. Consequently, boundary triangles that straddle the horizon are included and extend slightly below it. - Phi wrapping – for triangles that straddle the 0/2π azimuthal boundary, vertex phis below π are shifted by +2π before computing bounding-box limits, then wrapped back.
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
subdivision_level when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Triangles are excluded only if
all their vertices are below this elevation.
subdivision_level : int or None
Number of icosahedral subdivisions. If None, estimated from
angular_resolution. Typical range 0–5.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout. Use the geodesic_vertices column and the
vertices array in GridData.vertices for the true geometry.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | |
__init__(angular_resolution=2, cutoff_theta=0, subdivision_level=None, phi_rotation=0)
¶
Initialize the geodesic grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. subdivision_level : int | None, optional Subdivision level override. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
get_triangles()
¶
Return triangle vertex coordinates for visualization.
Returns¶
triangles : np.ndarray or None
Array of shape (n_faces, 3, 3) where triangles[i] contains
the three 3D unit-sphere vertices of triangle i. None if
the grid has not been built yet.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
131 132 133 134 135 136 137 138 139 140 141 142 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"geodesic"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
144 145 146 147 148 149 150 151 152 153 | |
FibonacciBuilder
¶
Bases: BaseGridBuilder
Fibonacci sphere grid with spherical Voronoi tessellation.
Points are distributed on the sphere using the Fibonacci lattice (golden-spiral method), which provides one of the most uniform point distributions achievable on a sphere without iterative optimisation. Each point then becomes the centre of a spherical Voronoi cell — the region of the sphere closer to that point than to any other. The resulting tessellation has no polar singularities and near-uniform cell areas.
The tessellation is computed by scipy.spatial.SphericalVoronoi.
Because Voronoi cells have curvilinear boundaries, the phi_min/max
and theta_min/max columns in the grid are axis-aligned bounding
boxes, not the true cell boundaries. They are unreliable for
spatial queries — use the voronoi_region column (vertex indices
into the SphericalVoronoi.vertices array) for exact geometry.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What n_points (resolution) means¶
Resolution is controlled by n_points, the number of Voronoi cells
in the hemisphere. When n_points is not supplied it is estimated
from angular_resolution via::
cell_area ≈ angular_resolution² (radians²)
n_points = max(10, round(2π / cell_area))
The approximate cell "diameter" (assuming a circular cell of equal area) is::
d ≈ 2 √(2π / n_points) (radians)
≈ 2 × angular_resolution
angular_resolution therefore has no direct geometric meaning for
this grid type — it is only a convenience for the n_points estimator.
Mathematical construction¶
-
Full-sphere Fibonacci lattice –
2 × n_pointspoints are generated on the unit sphere. Point i has::θᵢ = arccos(1 − 2(i + 0.5) / N) φᵢ = 2π (i + 0.5) / φ_golden (mod 2π)
where N = 2 × n_points and φ_golden = (1+√5)/2. The
+0.5 offset avoids placing points exactly at the poles.
2. Hemisphere filter – points with θ > π/2 − cutoff_theta
are discarded.
3. Spherical Voronoi tessellation –
scipy.spatial.SphericalVoronoi computes the Voronoi diagram
on the unit sphere. Regions are sorted so that vertices appear
in counter-clockwise order around each cell.
4. Bounding boxes – axis-aligned bounding boxes in (phi, theta)
are computed from the Voronoi vertex coordinates. These are
approximations only (see caveat above).
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to estimate
n_points when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Points below this elevation are
excluded before tessellation.
n_points : int or None
Target number of Voronoi cells in the hemisphere. If None,
estimated from angular_resolution.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Raises¶
ImportError
If scipy is not installed.
ValueError
If fewer than 4 points survive the hemisphere filter.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
Voronoi cell layout.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
__init__(angular_resolution=2, cutoff_theta=0, n_points=None, phi_rotation=0)
¶
Initialize the Fibonacci grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. n_points : int | None, optional Number of points to generate. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"fibonacci"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
135 136 137 138 139 140 141 142 143 144 | |
HTMBuilder
¶
Bases: BaseGridBuilder
Hierarchical Triangular Mesh (HTM) grid.
HTM divides the sphere into an octahedron (8 triangular faces), then
recursively subdivides each face into 4 smaller triangles by inserting
edge-midpoint vertices projected onto the unit sphere. The recursion
depth is controlled by htm_level. This produces a strictly
hierarchical triangulation: every triangle at level n is the union of
exactly 4 triangles at level n + 1.
Cell areas are approximately equal but not strictly so — area uniformity improves with level because the icosahedral edge-length asymmetry averages out over many subdivisions.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centres are the 3D Cartesian mean of the three triangle vertices, re-normalised onto the unit sphere.
What htm_level (resolution) means¶
The resolution is set by htm_level, not by angular_resolution.
angular_resolution is used only to estimate an appropriate level
when htm_level is not supplied explicitly. The heuristic is::
target_edge ≈ 2 × angular_resolution (degrees)
htm_level = min(15, ceil(log₂(90 / target_edge)))
The approximate triangle edge length at level n is::
edge ≈ 90° / 2ⁿ
| Level | Triangles (full sphere) | Approx edge |
|---|---|---|
| 0 | 8 | 90° |
| 1 | 32 | 45° |
| 2 | 128 | 22.5° |
| 3 | 512 | 11.25° |
| 4 | 2 048 | 5.6° |
| n | 8 × 4ⁿ | 90° / 2ⁿ |
Mathematical construction¶
- Octahedron – 6 vertices at ±x, ±y, ±z on the unit sphere, forming 8 triangular faces (4 northern, 4 southern).
-
Subdivision – for each triangle [v₀, v₁, v₂], three edge midpoints are computed and projected onto the unit sphere::
m₀ = normalise((v₀ + v₁) / 2) m₁ = normalise((v₁ + v₂) / 2) m₂ = normalise((v₂ + v₀) / 2)
The four children are [v₀, m₀, m₂], [v₁, m₁, m₀], [v₂, m₂, m₁],
and [m₀, m₁, m₂]. This is repeated htm_level times.
3. Hemisphere filter – a triangle is kept if any of its three
vertices satisfies theta ≤ π/2 − cutoff_theta. Boundary
triangles that straddle the horizon are therefore included and may
extend slightly below it.
4. Each leaf triangle becomes one cell; its centre, bounding box, and
three vertex coordinates are stored.
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
htm_level when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Triangles are excluded only when
all their vertices are below this elevation.
htm_level : int or None
HTM subdivision depth. If None, estimated from
angular_resolution. Practical range 0–15.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout.
HTM IDs in this implementation use a decimal-digit scheme
(parent_id × 10 + child_index) which diverges from the original
SDSS HTM binary-coded ID scheme. This is adequate for indexing but
should not be compared with external HTM catalogues.
References¶
Kunszt et al. (2001): "The Hierarchical Triangular Mesh" https://www.sdss.org/dr12/algorithms/htm/
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
__init__(angular_resolution=2, cutoff_theta=0, htm_level=None, phi_rotation=0)
¶
Initialize the HTM grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. htm_level : int | None, optional HTM subdivision level. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"htm"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
144 145 146 147 148 149 150 151 152 153 | |
get_htm_info()
¶
Get HTM-specific information.
Returns¶
info : dict
Keys: htm_level, n_triangles_full_sphere,
approx_edge_length_deg, approx_edge_length_arcmin.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
CellAggregator
¶
Polars-based per-cell aggregation helpers.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
aggregate_by_cell(df, value_var='VOD', method='mean')
staticmethod
¶
Aggregate values by cell_id.
Parameters¶
df : pl.DataFrame
Must contain cell_id and value_var columns.
value_var : str
Column to aggregate.
method : {'mean', 'median', 'std', 'count'}
Aggregation method.
Returns¶
pl.DataFrame
Two-column DataFrame: cell_id, value_var.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
AdaptedVODWorkflow
¶
Core VOD analysis workflow with polars-optimised loading and refined temporal matching.
All heavy lifting (filtering, grid operations) is delegated to
canvod.grids.analysis. This class is responsible only for
Icechunk I/O and orchestration.
Parameters¶
vod_store_path : Path or str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 | |
__init__(vod_store_path)
¶
Initialize the workflow.
Parameters¶
vod_store_path : Path | str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
96 97 98 99 100 101 102 103 104 105 106 | |
load_vod_data(group_name='reference_01_canopy_01', branch='main')
¶
Load a VOD dataset from the store.
Parameters¶
group_name : str Zarr group path inside the store. branch : str Icechunk branch to read from.
Returns¶
xr.Dataset Lazy-loaded VOD dataset.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
check_temporal_coverage_compatibility(main_ds, processed_ds, requested_time_range=None)
¶
Check whether processed_ds adequately covers a time range.
When requested_time_range is None the method checks that the
processed dataset covers at least 70 % of the main dataset's span.
When a range is given it verifies that both endpoints fall within the
processed dataset (with a 1-day tolerance).
Parameters¶
main_ds : xr.Dataset
Reference (unfiltered) dataset.
processed_ds : xr.Dataset
Filtered dataset to validate.
requested_time_range : tuple of date, optional
(start, end) to check against.
Returns¶
compatible : bool
coverage_info : dict
Diagnostic information with main_range, processed_range,
and requested_range.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
create_processed_data_fast_hampel_complete(start_date, end_date, force_recreate=False, window_hours=1.0, sigma_threshold=3.0, min_points=5, ultra_fast_mode=False, cell_batch_size=200, n_workers=None)
¶
Run the vectorised / ultra-fast Hampel pipeline end-to-end.
Delegates the actual filtering to
:func:canvod.grids.analysis.sigma_clip_filter.astropy_hampel_vectorized_fast
(or its ultra-fast variant) and persists the result on a
processing branch.
Parameters¶
start_date, end_date : date or datetime
Temporal extent to process.
force_recreate : bool
Overwrite existing filtered data.
window_hours : float
Hampel temporal window in hours.
sigma_threshold : float
MAD-based outlier threshold.
min_points : int
Minimum observations required per window.
ultra_fast_mode : bool
Use the pure-NumPy sigma-clip path (faster, less precise).
cell_batch_size : int
Number of cells per spatial batch.
n_workers : int, optional
Parallel workers. None → auto-detect.
Returns¶
str or None
Icechunk snapshot ID, or None if existing data was kept.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | |
create_processed_data_hampel_parallel_complete(start_date, end_date, force_recreate=False, threshold=3.0, min_obs_per_sid=20, spatial_batch_size=500, n_workers=None, temporal_agg=None, agg_method=None)
¶
Run the parallelised cell-SID Hampel pipeline end-to-end.
Loads the complete requested time range (no temporal chunking) and
applies
:func:canvod.grids.analysis.hampel_filtering.aggr_hampel_cell_sid_parallelized
with spatial batching.
Parameters¶
start_date, end_date : date or datetime
Temporal extent to process.
force_recreate : bool
Overwrite existing filtered data.
threshold : float
MAD-based outlier threshold.
min_obs_per_sid : int
Minimum observations per cell-SID combination.
spatial_batch_size : int
Cells per spatial batch.
n_workers : int, optional
Parallel workers. None → auto-detect.
temporal_agg : str, optional
Post-filtering aggregation frequency (e.g. '1H', '1D').
agg_method : str, optional
Aggregation method (e.g. 'mean').
Returns¶
str or None
Icechunk snapshot ID, or None if existing data was kept.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
run_complete_workflow(group_name='reference_01_canopy_01', branch='auto', time_range=None, **kwargs)
¶
Orchestrate a complete analysis run.
Auto-detection logic (branch='auto') looks for Hampel-filtered
data on the processing branch first. If found and temporally
compatible it is used directly; otherwise raw data from main is
returned.
Parameters¶
group_name : str
Zarr group for the raw VOD data.
branch : str
'auto' for detection, or an explicit branch name.
time_range : tuple of date, optional
(start, end) to select.
Returns¶
dict
Keys: final_data (Dataset), source_branch,
pre_filtered (bool), filter_type.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | |
create_hemigrid(grid_type, angular_resolution=10.0, **kwargs)
¶
Create hemisphere grid of specified type.
Factory function for creating various hemisphere grid types commonly used in GNSS analysis.
Parameters¶
grid_type : str Type of grid to create: - 'equal_area': Regular lat/lon grid with equal solid angle cells - 'equal_angle': Regular angular spacing (not recommended) - 'rectangular' or 'equirectangular': Simple rectangular grid - 'HTM': Hierarchical Triangular Mesh - 'geodesic': Geodesic sphere subdivision (icosahedron-based) - 'healpix': HEALPix grid (requires healpy) - 'fibonacci': Fibonacci sphere (requires scipy) angular_resolution : float, default 10.0 Angular resolution in degrees **kwargs Additional grid-specific parameters: - cutoff_theta : float - Maximum theta angle cutoff (degrees) - phi_rotation : float - Rotation angle (degrees) - subdivision_level : int - For geodesic/HTM grids - htm_level : int - For HTM grids specifically - nside : int - For HEALPix grids - n_points : int - For Fibonacci grids
Returns¶
GridData Complete hemisphere grid data structure
Examples¶
Equal area grid with 10° resolution¶
grid = create_hemigrid('equal_area', angular_resolution=10.0)
HTM grid with subdivision level 3¶
grid = create_hemigrid('HTM', angular_resolution=5.0, htm_level=3)
Geodesic grid¶
grid = create_hemigrid('geodesic', angular_resolution=5.0, subdivision_level=2)
Notes¶
Grid coordinates use navigation convention: - phi: azimuth angle, 0 to 2π (0 = North, π/2 = East, clockwise) - theta: polar angle from zenith, 0 to π/2 (0 = zenith, π/2 = horizon)
Source code in packages/canvod-grids/src/canvod/grids/__init__.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
Grid Core¶
Base class for hemisphere grid builders.
BaseGridBuilder
¶
Bases: ABC
Abstract base for hemispherical grid builders.
Parameters¶
angular_resolution : float Angular resolution in degrees cutoff_theta : float Maximum polar angle cutoff in degrees phi_rotation : float Rotation angle in degrees (applied to all phi values)
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
__init__(angular_resolution=2, cutoff_theta=0, phi_rotation=0)
¶
Initialize the grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
get_grid_type()
abstractmethod
¶
Get grid type identifier.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
84 85 86 | |
build()
¶
Build hemisphere grid.
Returns¶
GridData Complete grid data structure
Source code in packages/canvod-grids/src/canvod/grids/core/grid_builder.py
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
Grid data container for hemisphere grids.
GridData
dataclass
¶
Immutable container for hemispherical grid structure.
Parameters¶
grid : pl.DataFrame Grid cells with phi, theta, and bounds theta_lims : np.ndarray Theta band limits phi_lims : list[np.ndarray] Phi limits per theta band cell_ids : list[np.ndarray] Cell IDs per theta band grid_type : str Grid type identifier solid_angles : np.ndarray, optional Solid angles per cell [steradians] metadata : dict, optional Additional grid metadata voronoi : Any, optional Voronoi tessellation object (for Fibonacci grids) vertices : np.ndarray, optional 3D vertices (for triangular grids) points_xyz : np.ndarray, optional 3D point cloud (for Fibonacci grids) vertex_phi : np.ndarray, optional Vertex phi coordinates vertex_theta : np.ndarray, optional Vertex theta coordinates
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
coords
property
¶
Get cell coordinates.
ncells
property
¶
Number of cells in grid.
get_patches()
¶
Create matplotlib patches for polar visualization.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
67 68 69 70 71 72 73 74 75 76 77 78 | |
get_solid_angles()
¶
Calculate solid angle for each cell [steradians].
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
get_grid_stats()
¶
Get grid statistics including solid angle uniformity.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_data.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | |
Grid type definitions for hemisphere tessellation.
GridType
¶
Bases: Enum
Available grid projection types for hemispherical tessellation.
Source code in packages/canvod-grids/src/canvod/grids/core/grid_types.py
6 7 8 9 10 11 12 13 14 15 | |
Grid Builders¶
Equal-Area Grid¶
Equal-area grid implementation.
EqualAreaBuilder
¶
Bases: BaseGridBuilder
Equal solid angle tessellation using concentric theta bands.
The hemisphere is divided into annular bands of constant width in theta. Within each band the number of azimuthal (phi) sectors is chosen so that every cell subtends approximately the same solid angle. This is the only grid type that has been validated for scientific use in this codebase.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle measured from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means¶
angular_resolution (degrees) sets the width of each theta band.
All bands have this same width Δθ. The azimuthal width of cells varies
by band: near the zenith cells are wide in phi; near the horizon they are
narrow, so that the solid angle stays constant.
Mathematical construction¶
-
Target solid angle per cell is chosen equal to the solid angle of a cap of half-angle Δθ/2::
Ω_target = 2π (1 − cos(Δθ/2))
-
Zenith cap – a single cell covers [0, Δθ/2] in theta and the full azimuth [0, 2π).
-
Theta bands – edges are placed at Δθ/2, 3Δθ/2, 5Δθ/2, … up to π/2 − cutoff_theta. For each band [θ_inner, θ_outer] the band's total solid angle is::
Ω_band = 2π (cos θ_inner − cos θ_outer)
-
Phi divisions – the number of sectors in the band is::
n_phi = round(Ω_band / Ω_target)
Each sector spans Δφ = 2π / n_phi. The cell centre is placed at the geometric midpoint of its (phi, theta) rectangle.
Parameters¶
angular_resolution : float Theta-band width in degrees. Controls both the radial resolution and (indirectly, via the equal-area constraint) the azimuthal resolution. cutoff_theta : float Minimum elevation above the horizon in degrees. Bands whose outer edge is at or below this cutoff are omitted. In GNSS terms this is the satellite elevation mask angle. phi_rotation : float Rigid rotation applied to all phi coordinates after grid construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_area_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equal_area"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_area_grid.py
68 69 70 71 72 73 74 75 76 77 | |
Equal-Angle Grid¶
Equal-angle grid implementation.
EqualAngleBuilder
¶
Bases: BaseGridBuilder
Equal angular spacing in both theta and phi (NOT equal area).
Every cell is a rectangle of the same angular size Δθ × Δφ in the
(theta, phi) parameter space. Because solid angle depends on cos(theta),
cells near the zenith subtend more solid angle than cells near the
horizon. This makes the grid biased toward the zenith for any
solid-angle-weighted statistic. Not recommended for scientific
analysis – use EqualAreaBuilder instead.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith
What angular_resolution means¶
angular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The number of phi divisions is constant across
all bands::
n_phi = round(2π / Δθ)
and does not change with latitude.
Mathematical construction¶
- A zenith cap cell covers [0, Δθ/2] × [0, 2π).
- Theta band edges are placed at Δθ/2, 3Δθ/2, … up to π/2.
- Within every band, the full azimuth is split into
n_phisectors of equal width Δφ = 2π / n_phi. - Cell centres are at the midpoint of each (phi, theta) rectangle.
Parameters¶
angular_resolution : float Angular spacing in degrees, applied identically in both theta and phi. cutoff_theta : float Elevation mask angle in degrees (bands below this are omitted). phi_rotation : float Rigid azimuthal rotation applied after construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_angle_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equal_angle"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equal_angle_grid.py
53 54 55 56 57 58 59 60 61 62 | |
Equirectangular Grid¶
Equirectangular grid implementation.
EquirectangularBuilder
¶
Bases: BaseGridBuilder
Simple rectangular grid in (theta, phi) space.
The hemisphere is divided into a regular rectangular array: a constant
number of theta bands, each containing the same constant number of phi
sectors. Every cell is an identical rectangle in angular coordinates.
This is structurally identical to EqualAngleBuilder except for one
difference in the zenith treatment: EqualAngleBuilder collapses the
first band into a single zenith cap, while this builder does not — every
band has the same number of sectors.
Because solid angle depends on cos(theta), cells near the zenith subtend
more solid angle than cells near the horizon. This makes the grid
biased toward the zenith for any solid-angle-weighted statistic.
Not recommended for scientific analysis – use EqualAreaBuilder
instead.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What angular_resolution means¶
angular_resolution (degrees) is used as both the theta-band width
and the phi-sector width. The grid is therefore square in angular
coordinates::
n_theta = round((π/2 − cutoff) / Δθ)
n_phi = round(2π / Δθ)
total cells = n_theta × n_phi
Mathematical construction¶
- Theta edges are placed at
cutoff_theta,cutoff_theta + Δθ,cutoff_theta + 2Δθ, … up to π/2. - Phi edges are placed at 0, Δθ, 2Δθ, … up to 2π.
- Every (theta_band, phi_sector) combination produces one cell. The cell centre is the midpoint of the rectangle.
- No special zenith cap is created; the band nearest the zenith has the same number of phi sectors as all other bands.
Parameters¶
angular_resolution : float
Angular spacing in degrees, applied identically in both theta and phi.
cutoff_theta : float
Elevation mask angle in degrees. Bands whose inner edge is at or
below π/2 − cutoff_theta are omitted.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equirectangular_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"equirectangular"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/equirectangular_grid.py
64 65 66 67 68 69 70 71 72 73 | |
HEALPix Grid¶
HEALPix grid implementation.
HEALPixBuilder
¶
Bases: BaseGridBuilder
HEALPix tessellation (Hierarchical Equal Area isoLatitude Pixelization).
HEALPix partitions the sphere into 12 base pixels arranged at equal
latitudes. Each base pixel is recursively subdivided into 4 children,
producing 12 × nside² pixels on the full sphere, all with exactly
the same solid angle. This strict equal-area property makes HEALPix
the gold standard for pixelisations that must be unbiased under
solid-angle weighting.
This builder delegates the pixel geometry entirely to the healpy
library. It filters the full-sphere pixelisation down to the northern
hemisphere and stores approximate bounding boxes (phi_min/max,
theta_min/max) derived from the pixel resolution. The bounding
boxes are not the true pixel boundaries (which are curvilinear);
they are only approximations suitable for quick spatial queries. For
exact pixel membership use healpy.ang2pix directly.
Coordinate convention¶
HEALPix natively uses colatitude theta ∈ [0, π] (0 = North Pole)
and longitude phi ∈ [0, 2π). This matches the GNSS convention used
elsewhere in canvodpy: theta = 0 is the zenith, theta = π/2 is the
horizon. No coordinate transform is applied.
What nside (resolution) means¶
nside is the single resolution parameter of HEALPix. It must be a
power of 2. The key derived quantities are::
n_pixels = 12 × nside² (full sphere)
pixel_area = 4π / n_pixels (steradians, exact)
resolution ≈ √(pixel_area) (approximate angular diameter)
≈ 58.6° / nside (degrees)
| nside | Pixels (full) | Approx resolution | Pixel area (sr) |
|---|---|---|---|
| 1 | 12 | 58.6° | 1.049 |
| 2 | 48 | 29.3° | 0.262 |
| 4 | 192 | 14.7° | 0.065 |
| 8 | 768 | 7.3° | 0.016 |
| 16 | 3 072 | 3.7° | 0.004 |
| 32 | 12 288 | 1.8° | 0.001 |
When nside is not provided, it is estimated from angular_resolution
and rounded to the nearest power of 2::
nside_estimate = round_to_pow2( √(3/π) × 60 / angular_resolution )
Mathematical construction¶
HEALPix construction is performed entirely by healpy. At a high
level:
- The sphere is divided into 12 congruent base pixels (a curvilinear quadrilateral arrangement at three latitude zones: polar caps and equatorial belt).
- Each base pixel is subdivided into
nside²equal-area children using a hierarchical quadtree. - Pixel centres are returned by
healpy.pix2ang(nside, ipix)in RING ordering (pixels ordered by increasing colatitude). - This builder keeps only pixels with
theta ≤ π/2 − cutoff_theta(northern hemisphere above the elevation mask).
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
nside when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Pixels with colatitude
theta > π/2 − cutoff_theta (i.e. below the mask) are excluded.
nside : int or None
HEALPix resolution parameter. Must be a power of 2. If None,
estimated from angular_resolution.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Raises¶
ImportError
If healpy is not installed.
ValueError
If nside is not a power of 2.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
__init__(angular_resolution=2, cutoff_theta=0, nside=None, phi_rotation=0)
¶
Initialize the HEALPix grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. nside : int | None, optional HEALPix nside parameter. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"healpix"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
147 148 149 150 151 152 153 154 155 156 | |
get_healpix_info()
¶
Get HEALPix-specific information.
Returns¶
info : dict
Keys: nside, npix_total, pixel_area_sr,
pixel_area_arcmin2, resolution_arcmin,
resolution_deg, max_pixel_radius_deg.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/healpix_grid.py
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
Geodesic Grid¶
Geodesic grid implementation.
GeodesicBuilder
¶
Bases: BaseGridBuilder
Geodesic grid based on a subdivided icosahedron.
The sphere is tessellated into triangular cells by starting with an icosahedron (20 equilateral triangles) and recursively subdividing each triangle into four smaller triangles. All vertices are projected back onto the unit sphere after each subdivision step, so the final cells are spherical triangles. The grid has no polar singularity and provides near-uniform cell areas, though strict equal-area is not guaranteed — cell areas vary by a few percent depending on how they inherit the icosahedral symmetry axes.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centres are computed as the 3D Cartesian mean of the three vertices, re-normalised onto the unit sphere.
What angular_resolution means¶
angular_resolution is not used directly as a cell size. Instead it
is used only when subdivision_level is not explicitly supplied, to
estimate an appropriate subdivision level. The heuristic targets an
approximate triangle edge length of 2 × angular_resolution::
target_edge ≈ 2 × angular_resolution (degrees)
subdivision_level = ceil(log₂(63.4 / target_edge))
The number 63.4° is the edge length of a regular icosahedron inscribed in a unit sphere. Each subdivision halves the edge length, so the actual edge length at level n is approximately::
edge ≈ 63.4° / 2ⁿ (degrees)
The total number of triangles on the full sphere is 20 × 4ⁿ.
Roughly half fall in the northern hemisphere (exact count depends on
the hemisphere boundary).
Mathematical construction¶
- Icosahedron – 12 vertices placed at the intersections of three mutually perpendicular golden-ratio rectangles, normalised to the unit sphere. 20 triangular faces connect them.
- Subdivision – each triangle is split into 4 by inserting edge
midpoints. Each midpoint is projected onto the unit sphere
(re-normalised) before the next subdivision. This is repeated
subdivision_leveltimes. - Hemisphere filter – faces are kept if any of their three
vertices satisfies
theta ≤ π/2 − cutoff_theta. Consequently, boundary triangles that straddle the horizon are included and extend slightly below it. - Phi wrapping – for triangles that straddle the 0/2π azimuthal boundary, vertex phis below π are shifted by +2π before computing bounding-box limits, then wrapped back.
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
subdivision_level when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Triangles are excluded only if
all their vertices are below this elevation.
subdivision_level : int or None
Number of icosahedral subdivisions. If None, estimated from
angular_resolution. Typical range 0–5.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout. Use the geodesic_vertices column and the
vertices array in GridData.vertices for the true geometry.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | |
__init__(angular_resolution=2, cutoff_theta=0, subdivision_level=None, phi_rotation=0)
¶
Initialize the geodesic grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. subdivision_level : int | None, optional Subdivision level override. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
get_triangles()
¶
Return triangle vertex coordinates for visualization.
Returns¶
triangles : np.ndarray or None
Array of shape (n_faces, 3, 3) where triangles[i] contains
the three 3D unit-sphere vertices of triangle i. None if
the grid has not been built yet.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
131 132 133 134 135 136 137 138 139 140 141 142 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"geodesic"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/geodesic_grid.py
144 145 146 147 148 149 150 151 152 153 | |
Fibonacci Grid¶
Fibonacci sphere grid implementation.
FibonacciBuilder
¶
Bases: BaseGridBuilder
Fibonacci sphere grid with spherical Voronoi tessellation.
Points are distributed on the sphere using the Fibonacci lattice (golden-spiral method), which provides one of the most uniform point distributions achievable on a sphere without iterative optimisation. Each point then becomes the centre of a spherical Voronoi cell — the region of the sphere closer to that point than to any other. The resulting tessellation has no polar singularities and near-uniform cell areas.
The tessellation is computed by scipy.spatial.SphericalVoronoi.
Because Voronoi cells have curvilinear boundaries, the phi_min/max
and theta_min/max columns in the grid are axis-aligned bounding
boxes, not the true cell boundaries. They are unreliable for
spatial queries — use the voronoi_region column (vertex indices
into the SphericalVoronoi.vertices array) for exact geometry.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
What n_points (resolution) means¶
Resolution is controlled by n_points, the number of Voronoi cells
in the hemisphere. When n_points is not supplied it is estimated
from angular_resolution via::
cell_area ≈ angular_resolution² (radians²)
n_points = max(10, round(2π / cell_area))
The approximate cell "diameter" (assuming a circular cell of equal area) is::
d ≈ 2 √(2π / n_points) (radians)
≈ 2 × angular_resolution
angular_resolution therefore has no direct geometric meaning for
this grid type — it is only a convenience for the n_points estimator.
Mathematical construction¶
-
Full-sphere Fibonacci lattice –
2 × n_pointspoints are generated on the unit sphere. Point i has::θᵢ = arccos(1 − 2(i + 0.5) / N) φᵢ = 2π (i + 0.5) / φ_golden (mod 2π)
where N = 2 × n_points and φ_golden = (1+√5)/2. The
+0.5 offset avoids placing points exactly at the poles.
2. Hemisphere filter – points with θ > π/2 − cutoff_theta
are discarded.
3. Spherical Voronoi tessellation –
scipy.spatial.SphericalVoronoi computes the Voronoi diagram
on the unit sphere. Regions are sorted so that vertices appear
in counter-clockwise order around each cell.
4. Bounding boxes – axis-aligned bounding boxes in (phi, theta)
are computed from the Voronoi vertex coordinates. These are
approximations only (see caveat above).
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to estimate
n_points when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Points below this elevation are
excluded before tessellation.
n_points : int or None
Target number of Voronoi cells in the hemisphere. If None,
estimated from angular_resolution.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Raises¶
ImportError
If scipy is not installed.
ValueError
If fewer than 4 points survive the hemisphere filter.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
Voronoi cell layout.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
__init__(angular_resolution=2, cutoff_theta=0, n_points=None, phi_rotation=0)
¶
Initialize the Fibonacci grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. n_points : int | None, optional Number of points to generate. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"fibonacci"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/fibonacci_grid.py
135 136 137 138 139 140 141 142 143 144 | |
HTM Grid¶
HTM (Hierarchical Triangular Mesh) grid implementation.
HTMBuilder
¶
Bases: BaseGridBuilder
Hierarchical Triangular Mesh (HTM) grid.
HTM divides the sphere into an octahedron (8 triangular faces), then
recursively subdivides each face into 4 smaller triangles by inserting
edge-midpoint vertices projected onto the unit sphere. The recursion
depth is controlled by htm_level. This produces a strictly
hierarchical triangulation: every triangle at level n is the union of
exactly 4 triangles at level n + 1.
Cell areas are approximately equal but not strictly so — area uniformity improves with level because the icosahedral edge-length asymmetry averages out over many subdivisions.
Coordinate convention (physics / GNSS)¶
- phi ∈ [0, 2π) – azimuthal angle from North, clockwise (navigation convention)
- theta ∈ [0, π/2] – polar angle from zenith (0 = straight up, π/2 = horizon)
Cell centres are the 3D Cartesian mean of the three triangle vertices, re-normalised onto the unit sphere.
What htm_level (resolution) means¶
The resolution is set by htm_level, not by angular_resolution.
angular_resolution is used only to estimate an appropriate level
when htm_level is not supplied explicitly. The heuristic is::
target_edge ≈ 2 × angular_resolution (degrees)
htm_level = min(15, ceil(log₂(90 / target_edge)))
The approximate triangle edge length at level n is::
edge ≈ 90° / 2ⁿ
| Level | Triangles (full sphere) | Approx edge |
|---|---|---|
| 0 | 8 | 90° |
| 1 | 32 | 45° |
| 2 | 128 | 22.5° |
| 3 | 512 | 11.25° |
| 4 | 2 048 | 5.6° |
| n | 8 × 4ⁿ | 90° / 2ⁿ |
Mathematical construction¶
- Octahedron – 6 vertices at ±x, ±y, ±z on the unit sphere, forming 8 triangular faces (4 northern, 4 southern).
-
Subdivision – for each triangle [v₀, v₁, v₂], three edge midpoints are computed and projected onto the unit sphere::
m₀ = normalise((v₀ + v₁) / 2) m₁ = normalise((v₁ + v₂) / 2) m₂ = normalise((v₂ + v₀) / 2)
The four children are [v₀, m₀, m₂], [v₁, m₁, m₀], [v₂, m₂, m₁],
and [m₀, m₁, m₂]. This is repeated htm_level times.
3. Hemisphere filter – a triangle is kept if any of its three
vertices satisfies theta ≤ π/2 − cutoff_theta. Boundary
triangles that straddle the horizon are therefore included and may
extend slightly below it.
4. Each leaf triangle becomes one cell; its centre, bounding box, and
three vertex coordinates are stored.
Parameters¶
angular_resolution : float
Approximate angular resolution in degrees. Used only to derive
htm_level when that parameter is not given explicitly.
cutoff_theta : float
Elevation mask angle in degrees. Triangles are excluded only when
all their vertices are below this elevation.
htm_level : int or None
HTM subdivision depth. If None, estimated from
angular_resolution. Practical range 0–15.
phi_rotation : float
Rigid azimuthal rotation applied after construction, in degrees.
Notes¶
The theta_lims, phi_lims, and cell_ids fields of the returned
GridData are synthetic evenly-spaced arrays kept only for interface
compatibility with ring-based grids. They do not describe the actual
triangular cell layout.
HTM IDs in this implementation use a decimal-digit scheme
(parent_id × 10 + child_index) which diverges from the original
SDSS HTM binary-coded ID scheme. This is adequate for indexing but
should not be compared with external HTM catalogues.
References¶
Kunszt et al. (2001): "The Hierarchical Triangular Mesh" https://www.sdss.org/dr12/algorithms/htm/
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
__init__(angular_resolution=2, cutoff_theta=0, htm_level=None, phi_rotation=0)
¶
Initialize the HTM grid builder.
Parameters¶
angular_resolution : float, default 2 Angular resolution in degrees. cutoff_theta : float, default 0 Maximum polar angle cutoff in degrees. htm_level : int | None, optional HTM subdivision level. phi_rotation : float, default 0 Rotation angle in degrees.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | |
get_grid_type()
¶
Return the grid-type identifier string.
Returns¶
str
"htm"
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
144 145 146 147 148 149 150 151 152 153 | |
get_htm_info()
¶
Get HTM-specific information.
Returns¶
info : dict
Keys: htm_level, n_triangles_full_sphere,
approx_edge_length_deg, approx_edge_length_arcmin.
Source code in packages/canvod-grids/src/canvod/grids/grids_impl/htm_grid.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
Grid Operations¶
Cell assignment and vertex extraction for hemisphere grids.
Functions in this module operate on :class:~canvod.grids.core.GridData
instances and VOD xarray Datasets.
Cell assignment¶
add_cell_ids_to_vod_fast – vectorised KDTree lookup (preferred)
add_cell_ids_to_vod – element-wise fallback
add_cell_ids_to_ds_fast – dask-lazy variant for out-of-core data
Vertex / grid conversion¶
extract_grid_vertices – flat (x, y, z) arrays for 3-D visualisation
grid_to_dataset – xarray Dataset with vertices and solid angles
add_cell_ids_to_vod_fast(vod_ds, grid, grid_name)
¶
Assign grid cells to every observation in a VOD dataset (vectorised).
Uses a KDTree built from the grid cell centres for O(n log m) lookup.
Parameters¶
vod_ds : xr.Dataset
VOD dataset with phi(epoch, sid) and theta(epoch, sid)
coordinate variables and a VOD data variable.
grid : GridData
Hemisphere grid instance.
grid_name : str
Grid identifier used to name the output coordinate
(cell_id_<grid_name>).
Returns¶
xr.Dataset
vod_ds with an additional cell_id_<grid_name>(epoch, sid)
variable. Observations with non-finite φ or θ receive NaN.
Source code in packages/canvod-grids/src/canvod/grids/operations.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
add_cell_ids_to_vod(vod_ds, grid, grid_name)
¶
Assign grid cells to a VOD dataset (element-wise fallback).
Slower than :func:add_cell_ids_to_vod_fast; kept for cases where the
full dataset does not fit in memory as numpy arrays.
Parameters¶
vod_ds : xr.Dataset
VOD dataset with phi, theta, and VOD variables.
grid : GridData
Hemisphere grid instance.
grid_name : str
Grid identifier for the output coordinate name.
Returns¶
xr.Dataset
vod_ds with cell_id_<grid_name>(epoch, sid) added.
Source code in packages/canvod-grids/src/canvod/grids/operations.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
add_cell_ids_to_ds_fast(ds, grid, grid_name, data_var='VOD')
¶
Assign grid cells lazily via dask (avoids loading full arrays).
The output cell_id_<grid_name> variable is a dask array that
computes on access or save.
Parameters¶
ds : xr.Dataset
Dataset with dask-backed phi and theta arrays.
grid : GridData
Hemisphere grid instance.
grid_name : str
Grid identifier for the output coordinate name.
data_var : str
Name of the main data variable (used only for shape reference).
Returns¶
xr.Dataset
ds with a lazy cell_id_<grid_name>(epoch, sid) variable.
Source code in packages/canvod-grids/src/canvod/grids/operations.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
extract_grid_vertices(grid)
¶
Extract 3D vertices from hemisphere grid cells.
Dispatches to a grid-type–specific extractor. The returned arrays are flat (not per-cell); use them directly for 3-D scatter plots.
Parameters¶
grid : GridData Hemisphere grid instance.
Returns¶
x_vertices, y_vertices, z_vertices : np.ndarray Cartesian vertex coordinates on the unit sphere.
Source code in packages/canvod-grids/src/canvod/grids/operations.py
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 | |
grid_to_dataset(grid)
¶
Convert a HemiGrid to a unified xarray Dataset with vertices.
The returned Dataset carries cell centres, NaN-padded vertex arrays,
vertex counts, and solid angles — all indexed by cell_id.
Parameters¶
grid : GridData Hemisphere grid instance.
Returns¶
xr.Dataset
Dataset with dimensions (cell_id, vertex) and variables
cell_phi, cell_theta, vertices_phi, vertices_theta,
n_vertices, solid_angle.
Notes¶
This function is distinct from
:meth:HemiGridStorageAdapter._prepare_vertices_dataframe in
canvod-store. That method produces a long-form DataFrame for zarr
ragged-array storage; this one produces a rectangular xarray Dataset
suitable for analysis and visualisation.
Source code in packages/canvod-grids/src/canvod/grids/operations.py
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 | |
store_grid(grid, store, grid_name)
¶
Store grid in unified xarray format to Icechunk store.
Converts the grid to an xarray Dataset with vertex information
and writes it to the grids/ group in the store.
Parameters¶
grid : GridData Hemisphere grid instance to store. store Icechunk store instance (e.g., MyIcechunkStore). grid_name : str Grid identifier for storage path (e.g., 'equal_area_4deg').
Returns¶
str Snapshot ID from the commit.
Examples¶
from canvod.grids import create_hemigrid, store_grid grid = create_hemigrid(angular_resolution=4, grid_type='equal_area') snapshot_id = store_grid(grid, my_store, 'equal_area_4deg')
Source code in packages/canvod-grids/src/canvod/grids/operations.py
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 | |
load_grid(store, grid_name)
¶
Load a grid from Icechunk store.
Loads the grid structure from grids/{grid_name} and reconstructs
a GridData object.
Parameters¶
store Icechunk store instance (e.g., MyIcechunkStore). grid_name : str Grid identifier (e.g., 'equal_area_4deg').
Returns¶
GridData Reconstructed grid instance.
Examples¶
from canvod.grids import load_grid grid = load_grid(my_store, 'equal_area_4deg') print(f"Loaded {grid.ncells} cells")
Source code in packages/canvod-grids/src/canvod/grids/operations.py
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 | |
Aggregation¶
Per-cell aggregation of VOD observations onto hemisphere grids.
Top-level entry points¶
aggregate_data_to_grid – single-statistic spatial aggregation
compute_percell_timeseries – chunked (cell × time) time-series
Analysis helpers¶
compute_global_average – observation-count–weighted global mean
compute_regional_average – same, restricted to a cell subset
analyze_diurnal_patterns – hourly groupby
analyze_spatial_patterns – time-averaged spatial field
Convenience wrappers¶
compute_hemisphere_percell – daily, full hemisphere
compute_zenith_percell – daily, θ ≤ 30°
CellAggregator
¶
Polars-based per-cell aggregation helpers.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
aggregate_by_cell(df, value_var='VOD', method='mean')
staticmethod
¶
Aggregate values by cell_id.
Parameters¶
df : pl.DataFrame
Must contain cell_id and value_var columns.
value_var : str
Column to aggregate.
method : {'mean', 'median', 'std', 'count'}
Aggregation method.
Returns¶
pl.DataFrame
Two-column DataFrame: cell_id, value_var.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
aggregate_data_to_grid(data_ds, grid, value_var='VOD', cell_var='cell_id_equal_area_2deg', sid=None, time_range=None, stat='median')
¶
Aggregate VOD data across all timestamps and SIDs to per-cell statistics.
Parameters¶
data_ds : xr.Dataset
Full VOD dataset from the Icechunk store.
grid : GridData
Grid definition.
value_var : str
Name of the VOD variable.
cell_var : str
Name of the cell-ID variable in data_ds.
sid : list[str], optional
Satellite IDs to include. None → all.
time_range : tuple, optional
(start, end) datetimes for epoch filtering.
stat : {'mean', 'median', 'std'}
Statistic to compute per cell.
Returns¶
np.ndarray
Array of length grid.ncells with per-cell aggregated values
(NaN where no observations exist).
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
compute_percell_timeseries(data_ds, grid, value_var='VOD', cell_var='cell_id_equal_area_2deg', theta_range=None, phi_range=None, selected_sids=None, time_range=None, temporal_resolution='1D', chunk_days=21, min_obs_per_cell_time=1)
¶
Compute time series per cell with SID aggregation.
Processing is chunked over time to bound memory usage.
Parameters¶
data_ds : xr.Dataset
Full VOD dataset.
grid : GridData
Grid definition.
value_var : str
VOD variable name.
cell_var : str
Cell-ID variable name.
theta_range : tuple, optional
(min_deg, max_deg) elevation filter.
phi_range : tuple, optional
(min_deg, max_deg) azimuth filter (wraps at 360°).
selected_sids : list[str], optional
Satellite IDs to include.
time_range : tuple, optional
(start, end) epoch slice.
temporal_resolution : str
Pandas/polars frequency string (e.g. "1D", "30min").
chunk_days : int
Days per processing chunk.
min_obs_per_cell_time : int
Minimum SID observations per (cell, time-bin) to retain.
Returns¶
xr.Dataset
Dataset with dimensions (cell, time) and variables
cell_timeseries, cell_weights, cell_counts,
cell_theta, cell_phi.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | |
compute_global_average(percell_ds)
¶
Compute observation-count–weighted global average from per-cell data.
Parameters¶
percell_ds : xr.Dataset
Output of :func:compute_percell_timeseries.
Returns¶
xr.Dataset
Variables: global_timeseries, spatial_std,
total_weights, active_cells.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | |
compute_regional_average(percell_ds, region_cells)
¶
Compute observation-count–weighted average for a cell subset.
Parameters¶
percell_ds : xr.Dataset
Output of :func:compute_percell_timeseries.
region_cells : array-like
Cell IDs defining the region.
Returns¶
xr.DataArray Weighted regional mean time series.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 | |
analyze_diurnal_patterns(percell_ds)
¶
Compute hourly means from per-cell time series.
Parameters¶
percell_ds : xr.Dataset
Output of :func:compute_percell_timeseries.
Returns¶
xr.Dataset
Grouped by time.hour.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 | |
analyze_spatial_patterns(percell_ds)
¶
Compute time-averaged spatial field.
Parameters¶
percell_ds : xr.Dataset
Output of :func:compute_percell_timeseries.
Returns¶
xr.Dataset Time-averaged dataset.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 | |
compute_hemisphere_percell(data_ds, grid, **kwargs)
¶
Daily per-cell time series for the full hemisphere.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
447 448 449 450 451 452 453 454 455 | |
compute_zenith_percell(data_ds, grid, **kwargs)
¶
Daily per-cell time series restricted to θ ≤ 30°.
Source code in packages/canvod-grids/src/canvod/grids/aggregation.py
458 459 460 461 462 463 464 465 466 467 468 469 470 | |
Analysis¶
Filtering¶
Global (dataset-wide) outlier filters for gridded VOD data.
Classes¶
Filter – abstract base; compute_mask / apply contract.
ZScoreFilter – mean ± k·σ rejection.
IQRFilter – Q1 – f·IQR / Q3 + f·IQR rejection.
RangeFilter – hard min/max bounds.
PercentileFilter – lower/upper percentile bounds.
CustomFilter – user-supplied callable mask.
FilterPipeline – sequential or combined multi-filter application.
Convenience functions¶
create_zscore_filter – one-liner z-score filter.
create_range_filter – one-liner range filter.
Notes¶
- Filters never modify original data.
applyreturns a newxr.Datasetwith<var>_filtered_<n>andmask_<n>variables appended. - Both numpy and dask-backed arrays are supported; dask paths compute only the scalar statistics eagerly while the mask itself stays lazy.
Filter
¶
Bases: ABC
Base class for all filters. Filters NEVER modify original data.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
__init__(name)
¶
Initialize the filter.
Parameters¶
name : str Filter name.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
compute_mask(data, **kwargs)
abstractmethod
¶
Compute boolean mask (True = keep, False = remove).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
62 63 64 65 66 67 68 69 | |
apply(ds, var_name, output_suffix=None, **kwargs)
¶
Apply filter to ds, returning a copy with filtered variable added.
New variables¶
<var_name>_filtered_<suffix> : filtered data (NaN where masked).
mask_<suffix> : boolean keep-mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
ZScoreFilter
¶
Bases: Filter
Remove statistical outliers using z-score method.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
131 132 133 | |
compute_mask(data, threshold=3.0)
¶
Compute z-score mask.
Parameters¶
data : xr.DataArray Input data. threshold : float Z-score threshold (default: 3.0).
Returns¶
xr.DataArray Boolean mask (True = keep).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |
IQRFilter
¶
Bases: Filter
Remove outliers using Interquartile Range method.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
169 170 171 | |
compute_mask(data, factor=1.5)
¶
Compute IQR mask.
Parameters¶
data : xr.DataArray Input data. factor : float IQR factor (default: 1.5).
Returns¶
xr.DataArray Boolean mask (True = keep).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
RangeFilter
¶
Bases: Filter
Filter values outside specified range.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
216 217 218 | |
compute_mask(data, min_value=None, max_value=None)
¶
Compute range mask.
Parameters¶
data : xr.DataArray Input data. min_value : float, optional Minimum allowed value. max_value : float, optional Maximum allowed value.
Returns¶
xr.DataArray Boolean mask (True = keep).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 | |
PercentileFilter
¶
Bases: Filter
Filter values outside percentile range.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
256 257 258 | |
compute_mask(data, lower=5.0, upper=95.0)
¶
Compute percentile mask.
Parameters¶
data : xr.DataArray Input data. lower : float Lower percentile (0–100). upper : float Upper percentile (0–100).
Returns¶
xr.DataArray Boolean mask (True = keep).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
CustomFilter
¶
Bases: Filter
Apply a user-supplied callable as filter.
Parameters¶
name : str
Filter identifier.
func : callable
(xr.DataArray, **kwargs) -> xr.DataArray returning a boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
__init__(name, func)
¶
Initialize the custom filter.
Parameters¶
name : str Filter identifier. func : Callable[..., xr.DataArray] Callable returning a boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
300 301 302 303 304 305 306 307 308 309 310 311 312 | |
compute_mask(data, **kwargs)
¶
Apply custom function.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
314 315 316 317 318 319 320 | |
FilterPipeline
¶
Manage multiple filters applied sequentially or combined.
Non-destructive: creates new DataArrays, never modifies originals.
Parameters¶
ds : xr.Dataset
Input dataset.
var_name : str
Variable to filter (default: 'VOD').
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 | |
__init__(ds, var_name='VOD')
¶
Initialize the filter pipeline.
Parameters¶
ds : xr.Dataset Input dataset. var_name : str, default "VOD" Variable to filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 | |
add_filter(filter_obj, **kwargs)
¶
Add filter to pipeline.
Parameters¶
filter_obj : Filter or str
Filter instance or short name
('zscore', 'iqr', 'range', 'percentile').
**kwargs
Parameters forwarded to compute_mask.
Returns¶
FilterPipeline Self (for chaining).
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
apply(mode='sequential', output_name=None)
¶
Apply all filters in the pipeline.
Parameters¶
mode : {'sequential', 'combined'}
'sequential' – masks accumulate (AND) after each filter;
intermediate filtered variables are written.
'combined' – all masks computed independently on the
original data, then AND-ed once.
output_name : str, optional
Alias for the final filtered variable.
Returns¶
xr.Dataset Dataset with filtered variables appended.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 | |
summary()
¶
Return a human-readable summary of the pipeline.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
534 535 536 537 538 539 540 541 | |
create_zscore_filter(ds, var_name='VOD', threshold=3.0, suffix='zscore')
¶
One-liner z-score filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
549 550 551 552 553 554 555 556 | |
create_range_filter(ds, var_name='VOD', min_value=None, max_value=None, suffix='range')
¶
One-liner range filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/filtering.py
559 560 561 562 563 564 565 566 567 568 569 | |
Per-Cell Filtering¶
Per-cell outlier filters for gridded VOD data.
Unlike the global filters in :mod:~canvod.grids.analysis.filtering, these
operate independently on each grid cell, preserving spatial structure
while removing temporal outliers within cells.
Classes¶
PerCellFilter – abstract base with auto cell-id detection. PerCellIQRFilter – per-cell IQR rejection. PerCellZScoreFilter – per-cell z-score rejection. PerCellRangeFilter – per-cell hard bounds. PerCellPercentileFilter – per-cell percentile bounds. PerCellFilterPipeline – sequential or combined multi-filter application.
Convenience functions¶
create_per_cell_iqr_filter – one-liner per-cell IQR.
create_per_cell_zscore_filter – one-liner per-cell z-score.
PerCellFilter
¶
Bases: ABC
Base class for per-cell filtering operations.
Sub-classes implement :meth:compute_cell_mask for a single cell's
1-D data array; the base class handles iteration over cells,
auto-detection of the cell_id_* variable, and output assembly.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
__init__(filter_name)
¶
Initialize the per-cell filter.
Parameters¶
filter_name : str Filter name.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
47 48 49 50 51 52 53 54 55 56 | |
compute_cell_mask(cell_data, **kwargs)
abstractmethod
¶
Return a boolean keep-mask for one cell's data.
Parameters¶
cell_data : np.ndarray 1-D array of values for one cell across time.
Returns¶
np.ndarray Boolean mask (True = keep).
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
apply(ds, var_name='VOD', cell_id_var=None, output_suffix=None, min_observations=5, **kwargs)
¶
Apply per-cell filtering to ds.
Parameters¶
ds : xr.Dataset
Input dataset (must contain a cell_id_* variable).
var_name : str
Variable to filter.
cell_id_var : str, optional
Cell-ID variable name. Auto-detected from cell_id_* if None.
output_suffix : str, optional
Suffix for output variables (default: filter_name).
min_observations : int
Minimum observations per cell required for filtering.
**kwargs
Forwarded to :meth:compute_cell_mask.
Returns¶
xr.Dataset
Copy of ds with <var>_filtered_<suffix> and
mask_<suffix> appended.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | |
PerCellIQRFilter
¶
Bases: PerCellFilter
Per-cell IQR outlier rejection.
Values outside [Q1 − factor·IQR, Q3 + factor·IQR] are removed
independently within each cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
239 240 241 | |
compute_cell_mask(cell_data, factor=1.5)
¶
IQR mask for a single cell.
Parameters¶
cell_data : np.ndarray 1-D cell data. factor : float IQR multiplier (default 1.5).
Returns¶
np.ndarray Boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
PerCellZScoreFilter
¶
Bases: PerCellFilter
Per-cell z-score outlier rejection.
Values with |z| > threshold are removed independently within each cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
284 285 286 | |
compute_cell_mask(cell_data, threshold=3.0)
¶
Z-score mask for a single cell.
Parameters¶
cell_data : np.ndarray 1-D cell data. threshold : float Z-score threshold (default 3.0).
Returns¶
np.ndarray Boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | |
PerCellRangeFilter
¶
Bases: PerCellFilter
Per-cell hard-bound range filter.
Identical bounds applied to every cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
325 326 327 | |
compute_cell_mask(cell_data, min_value=None, max_value=None)
¶
Range mask for a single cell.
Parameters¶
cell_data : np.ndarray 1-D cell data. min_value : float, optional Minimum allowed value. max_value : float, optional Maximum allowed value.
Returns¶
np.ndarray Boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | |
PerCellPercentileFilter
¶
Bases: PerCellFilter
Per-cell percentile-bound filter.
Bounds are computed independently within each cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | |
__init__()
¶
Initialize the filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
366 367 368 | |
compute_cell_mask(cell_data, lower=5.0, upper=95.0)
¶
Percentile mask for a single cell.
Parameters¶
cell_data : np.ndarray 1-D cell data. lower : float Lower percentile (0–100). upper : float Upper percentile (0–100).
Returns¶
np.ndarray Boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | |
PerCellFilterPipeline
¶
Sequential or combined multi-filter pipeline for per-cell filtering.
Parameters¶
ds : xr.Dataset
Input dataset.
var_name : str
Variable to filter (default: 'VOD').
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | |
__init__(ds, var_name='VOD')
¶
Initialize the filter pipeline.
Parameters¶
ds : xr.Dataset Input dataset. var_name : str, default "VOD" Variable to filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
add_filter(filter_obj, **kwargs)
¶
Add a filter.
Parameters¶
filter_obj : PerCellFilter or str
Filter instance or short name
('iqr', 'zscore', 'range', 'percentile').
**kwargs
Parameters forwarded to the filter.
Returns¶
PerCellFilterPipeline Self (for chaining).
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 | |
apply(mode='sequential', output_name=None)
¶
Apply all filters.
Parameters¶
mode : {'sequential', 'combined'}
'sequential' – each filter operates on the previous output.
'combined' – all masks computed on the original, then AND-ed.
output_name : str, optional
Alias for the final filtered variable.
Returns¶
xr.Dataset Dataset with filtered results.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 | |
create_per_cell_iqr_filter(ds, var_name='VOD', factor=1.5, cell_id_var=None, min_observations=5)
¶
One-liner per-cell IQR filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 | |
create_per_cell_zscore_filter(ds, var_name='VOD', threshold=3.0, cell_id_var=None, min_observations=5)
¶
One-liner per-cell z-score filter.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_filtering.py
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 | |
Hampel Filtering¶
Parallelized Hampel filtering for gridded VOD data.
Spatial-batch multiprocessing Hampel filter with complete temporal coverage (no temporal chunking). Each (cell_id, SID) time series is filtered independently using median absolute deviation (MAD).
Functions¶
process_spatial_batch_worker – picklable worker for one spatial batch.
hampel_cell_sid_parallelized – main entry point (no temporal aggregation).
aggr_hampel_cell_sid_parallelized – with optional temporal aggregation.
Notes¶
- Worker function is module-level so it can be pickled by
multiprocessing.Pool. - Default spatial batch size is 500 cells; tune based on available memory.
- Expected throughput: 300–700 K cell-SID combinations / s on a typical multi-core machine.
process_spatial_batch_worker(args)
¶
Process a single spatial batch for one SID.
Designed to be pickled and dispatched to worker processes. Returns compact index lists to minimise IPC memory transfer.
Parameters¶
args : tuple
(batch_cells, vod_values_sid, cell_ids_sid, valid_indices,
threshold, min_obs_per_sid, batch_idx, sid_idx)
batch_cells : np.ndarray
Cell IDs in this batch.
vod_values_sid : np.ndarray
VOD values for the current SID (valid entries only).
cell_ids_sid : np.ndarray
Corresponding cell IDs (same length as *vod_values_sid*).
valid_indices : np.ndarray
Original epoch indices of the valid entries.
threshold : float
MAD threshold for outlier detection.
min_obs_per_sid : int
Minimum observations required per cell to run filter.
batch_idx : int
Batch index (for bookkeeping).
sid_idx : int
SID index (for bookkeeping).
Returns¶
dict
Keys: batch_idx, sid_idx, outlier_indices,
processing_indices, combinations, filtered.
Source code in packages/canvod-grids/src/canvod/grids/analysis/hampel_filtering.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
hampel_cell_sid_parallelized(vod_ds, grid_name='equal_area_2deg', threshold=3.0, min_obs_per_sid=20, spatial_batch_size=500, n_workers=None)
¶
Parallelized cell–SID Hampel filter with complete temporal coverage.
Each (cell_id, SID) time series is filtered independently using global (non-chunked) statistics. Spatial work is distributed across n_workers processes.
Parameters¶
vod_ds : xr.Dataset
Input dataset containing 'VOD' and a cell_id_<grid_name>
variable, with dimensions (epoch, sid).
grid_name : str
Grid identifier used to locate the cell-ID variable, e.g.
'equal_area_2deg'.
threshold : float
MAD multiplier for outlier detection.
min_obs_per_sid : int
Minimum valid observations per cell-SID to run the filter.
spatial_batch_size : int
Number of cells per parallel batch.
n_workers : int or None
Number of worker processes. Defaults to min(cpu_count(), 8).
Returns¶
xr.Dataset Copy of vod_ds with additional variables:
* ``VOD_filtered_hampel`` – filtered VOD (outliers set to NaN).
* ``hampel_processing_mask`` – boolean mask of processed
observations.
Dataset-level attrs include full processing metadata.
Source code in packages/canvod-grids/src/canvod/grids/analysis/hampel_filtering.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 | |
aggr_hampel_cell_sid_parallelized(vod_ds, grid_name='equal_area_2deg', threshold=3.0, min_obs_per_sid=20, spatial_batch_size=500, n_workers=None, temporal_agg=None, agg_method='mean')
¶
Parallelized cell–SID Hampel filter with optional temporal aggregation.
Each (cell_id, SID) series is filtered independently. When temporal_agg is set, data is first binned into temporal windows before filtering; φ, θ and cell IDs are reassigned consistently on the output.
Parameters¶
vod_ds : xr.Dataset
Input dataset with 'VOD', 'phi', 'theta' and a
cell_id_<grid_name> variable.
grid_name : str
Grid identifier (e.g. 'equal_area_2deg'). Used to locate
or create the cell-ID variable.
threshold : float
MAD multiplier.
min_obs_per_sid : int
Minimum valid observations per cell-SID.
spatial_batch_size : int
Cells per parallel batch.
n_workers : int or None
Worker count (defaults to min(cpu_count(), 8)).
temporal_agg : str or None
Temporal aggregation frequency (e.g. '1H', '1D').
None → no aggregation.
agg_method : str
'mean' or 'median' for temporal aggregation.
Returns¶
xr.Dataset
Dataset with 'VOD' (aggregated raw), 'VOD_filtered_hampel',
'hampel_outlier_mask', 'hampel_processing_mask',
reassigned phi, theta and cell_id_<grid_name>.
Source code in packages/canvod-grids/src/canvod/grids/analysis/hampel_filtering.py
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 | |
Sigma-Clip Filtering¶
Vectorised numba-JIT Hampel filter with astropy sigma-clipping fallback.
Two complementary high-performance filtering strategies for gridded VOD data:
- Vectorised + numba (
astropy_hampel_vectorized_fast) – sliding-window Hampel filter compiled withnumba.jit. Processes temporal chunks in cell batches; targets sub-5-minute runtimes for ~1.5 years of data. - Ultra-fast (
astropy_hampel_ultra_fast) – pure-numpy vectorisation backed byastropy.stats.sigma_clip. Drops the per-window granularity in exchange for extreme throughput.
Functions¶
vectorized_sliding_window_hampel – numba-compiled core loop.
process_cell_batch_vectorized – batch dispatcher for one temporal chunk.
astropy_hampel_vectorized_fast – full pipeline (numba path).
astropy_hampel_ultra_fast – full pipeline (astropy path).
Notes¶
vectorized_sliding_window_hampelusesnumba.prangeso the outer loop is distributed across all available cores automatically.- The 1.4826 MAD scaling factor matches the convention used by
astropy.stats.mad_std. - Both top-level functions expect a
cell_id_<grid_name>variable already present in the input dataset (see :func:canvod.grids.add_cell_ids_to_vod_fast).
vectorized_sliding_window_hampel(data, times, window_ns, sigma_threshold=3.0, min_points=5)
¶
Sliding-window Hampel filter compiled with numba.
Each point is compared against the robust statistics (median,
scaled MAD) of its temporal neighbourhood. Points whose deviation
exceeds sigma_threshold × 1.4826 × MAD are flagged as outliers
and set to NaN.
Parameters¶
data : np.ndarray
1-D array of values to filter.
times : np.ndarray
1-D array of timestamps as int64 nanoseconds.
window_ns : int
Half-window size in nanoseconds.
sigma_threshold : float, optional
Number of scaled-MAD units for the outlier boundary.
min_points : int, optional
Minimum number of finite points required in the window to
attempt filtering; points in smaller windows are left unchanged.
Returns¶
filtered_data : np.ndarray
Copy of data with outliers replaced by NaN.
outlier_mask : np.ndarray
Boolean mask; True where an outlier was detected.
Source code in packages/canvod-grids/src/canvod/grids/analysis/sigma_clip_filter.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
process_cell_batch_vectorized(cell_batch, vod_chunk, times_chunk, cell_ids_chunk, window_hours, sigma_threshold, min_points)
¶
Apply the numba Hampel filter to a subset of cells in one temporal chunk.
Parameters¶
cell_batch : np.ndarray
1-D array of cell IDs to process in this batch.
vod_chunk : np.ndarray
2-D (epoch, sid) VOD values for the current temporal chunk.
times_chunk : np.ndarray
1-D datetime64 timestamps for the chunk.
cell_ids_chunk : np.ndarray
2-D (epoch, sid) cell-ID array matching vod_chunk.
window_hours : float
Half-window size in hours.
sigma_threshold : float
Outlier threshold in scaled-MAD units.
min_points : int
Minimum finite points required per window.
Returns¶
filtered_chunk : np.ndarray Filtered copy of vod_chunk. outlier_chunk : np.ndarray Boolean outlier mask with the same shape as vod_chunk.
Source code in packages/canvod-grids/src/canvod/grids/analysis/sigma_clip_filter.py
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
astropy_hampel_vectorized_fast(vod_ds, grid_name='equal_area_2deg', window_hours=1.0, sigma_threshold=3.0, min_points=5, cell_batch_size=200, n_workers=None)
¶
Numba-accelerated sliding-window Hampel filter over a VOD dataset.
Temporal chunks (as stored in the dask-backed dataset) are iterated
sequentially; within each chunk the unique cells are split into
batches of cell_batch_size and dispatched to
:func:process_cell_batch_vectorized, which in turn calls the
numba-compiled :func:vectorized_sliding_window_hampel.
Parameters¶
vod_ds : xr.Dataset
VOD dataset containing a cell_id_<grid_name> variable.
grid_name : str, optional
Suffix used to locate the cell-ID variable
(cell_id_<grid_name>).
window_hours : float, optional
Half-window size in hours for the sliding window.
sigma_threshold : float, optional
Outlier boundary in scaled-MAD units.
min_points : int, optional
Minimum finite points required in a window.
cell_batch_size : int, optional
Number of cells per batch (trades memory for cache locality).
n_workers : int or None, optional
Unused in this implementation; reserved for future parallel
chunk processing.
Returns¶
result_ds : xr.Dataset Copy of vod_ds with two additional variables:
``VOD_filtered``
Filtered VOD (outliers → NaN).
``hampel_outlier_mask``
Boolean mask; ``True`` at outlier positions.
Raises¶
ValueError If the expected cell-ID variable is missing from vod_ds.
Source code in packages/canvod-grids/src/canvod/grids/analysis/sigma_clip_filter.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 | |
astropy_hampel_ultra_fast(vod_ds, grid_name='equal_area_2deg', window_hours=1.0, sigma_threshold=3.0, min_points=5)
¶
Pure-numpy sigma-clipping filter via astropy.stats.
Each (cell, SID) time series is clipped in one shot using
:func:astropy.stats.sigma_clip with median centering and MAD
scale estimation. No per-window temporal granularity is applied;
this trades precision for throughput.
Parameters¶
vod_ds : xr.Dataset
VOD dataset containing a cell_id_<grid_name> variable.
grid_name : str, optional
Suffix for the cell-ID variable.
window_hours : float, optional
Unused in ultra-fast mode; kept for API compatibility.
sigma_threshold : float, optional
Sigma-clipping threshold passed to astropy.stats.sigma_clip.
min_points : int, optional
Minimum finite observations required to attempt clipping.
Returns¶
result_ds : xr.Dataset
Copy of vod_ds with VOD_filtered and
hampel_outlier_mask variables added.
Raises¶
ValueError If the expected cell-ID variable is missing from vod_ds.
Source code in packages/canvod-grids/src/canvod/grids/analysis/sigma_clip_filter.py
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | |
Masking¶
Spatial masking for hemispherical grid cells.
Provides tools to create boolean masks for selecting subsets of grid cells based on geometric constraints, data quality, or custom criteria.
Classes¶
SpatialMask – builder for boolean cell-selection masks.
Convenience functions¶
create_hemisphere_mask – north / south / east / west mask.
create_elevation_mask – elevation-angle-based mask.
SpatialMask
¶
Create spatial masks for grid cells.
Masks are boolean arrays where True = include cell,
False = exclude cell. Multiple constraints can be combined
with AND or OR logic.
Parameters¶
grid : GridData Grid instance.
Examples¶
mask = SpatialMask(grid) mask.add_phi_range(0, np.pi) # Northern hemisphere mask.add_theta_range(0, np.pi / 3) # Exclude low elevations mask.add_quality_threshold('mean_snr', min_value=40) spatial_mask = mask.compute() # Returns boolean array
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
__init__(grid)
¶
Initialize the spatial mask builder.
Parameters¶
grid : GridData Grid instance.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
49 50 51 52 53 54 55 56 57 58 59 60 | |
add_phi_range(phi_min, phi_max, degrees=False)
¶
Add azimuth angle constraint.
Parameters¶
phi_min : float
Minimum azimuth angle.
phi_max : float
Maximum azimuth angle.
degrees : bool
If True, angles are in degrees; otherwise radians.
Returns¶
SpatialMask Self for chaining.
Notes¶
Handles wraparound at 0°/360° correctly.
Example: phi_range(350, 10) includes both 350°–360° and 0°–10°.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
add_theta_range(theta_min, theta_max, degrees=False)
¶
Add polar angle (zenith angle) constraint.
Parameters¶
theta_min : float
Minimum polar angle (0 = zenith).
theta_max : float
Maximum polar angle (π/2 = horizon).
degrees : bool
If True, angles are in degrees; otherwise radians.
Returns¶
SpatialMask Self for chaining.
Notes¶
theta = 0° is zenith (straight up), theta = 90° is horizon.
To exclude low elevations, use theta_max < 90°.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
add_elevation_range(elev_min, elev_max, degrees=True)
¶
Add elevation angle constraint (complementary to theta).
Parameters¶
elev_min : float
Minimum elevation angle (0 = horizon, 90 = zenith).
elev_max : float
Maximum elevation angle.
degrees : bool
If True, angles are in degrees; otherwise radians.
Default: True.
Returns¶
SpatialMask Self for chaining.
Notes¶
elevation = 90° − theta. This is more intuitive for users
who think in elevation angles.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
add_cell_ids(cell_ids)
¶
Include specific cell IDs.
Parameters¶
cell_ids : list[int] or np.ndarray Cell IDs to include.
Returns¶
SpatialMask Self for chaining.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
add_exclude_cell_ids(cell_ids)
¶
Exclude specific cell IDs.
Parameters¶
cell_ids : list[int] or np.ndarray Cell IDs to exclude.
Returns¶
SpatialMask Self for chaining.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
add_quality_threshold(var_name, min_value=None, max_value=None)
¶
Add data quality threshold based on grid cell properties.
Parameters¶
var_name : str
Variable name in grid (e.g. 'mean_snr',
'n_observations').
min_value : float, optional
Minimum allowed value.
max_value : float, optional
Maximum allowed value.
Returns¶
SpatialMask Self for chaining.
Raises¶
ValueError If var_name doesn't exist in the grid DataFrame.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
add_boundary_cells(exclude=True)
¶
Include or exclude boundary cells.
Parameters¶
exclude : bool
If True, exclude boundary cells; if False, include
only boundary cells.
Returns¶
SpatialMask Self for chaining.
Raises¶
ValueError
If the grid does not have an 'is_boundary' column.
Notes¶
Only works if the grid DataFrame contains an 'is_boundary'
column.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
add_custom_mask(mask, name='custom')
¶
Add custom mask or mask-generating callable.
Parameters¶
mask : np.ndarray or callable
* If array: boolean mask of shape (ncells,).
* If callable: function (grid: GridData) -> np.ndarray.
name : str
Label for this mask (used in summary).
Returns¶
SpatialMask Self for chaining.
Raises¶
ValueError If the resulting array has wrong shape or dtype.
Examples¶
custom = np.array([True, False, True, ...]) mask.add_custom_mask(custom)
def high_snr_north(grid): ... snr = grid.grid['mean_snr'].to_numpy() ... phi = grid.grid['phi'].to_numpy() ... return (snr > 40) & (phi < np.pi) mask.add_custom_mask(high_snr_north)
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
add_radial_sector(center_phi, sector_width, theta_min=0.0, theta_max=np.pi / 2, degrees=True)
¶
Add radial sector (wedge) mask.
Parameters¶
center_phi : float
Centre azimuth of sector.
sector_width : float
Full angular width of sector.
theta_min : float
Minimum polar angle (radial inner bound).
theta_max : float
Maximum polar angle (radial outer bound).
degrees : bool
If True, all angles in degrees; otherwise radians.
Returns¶
SpatialMask Self for chaining.
Examples¶
Northern sector, 30° wide, excluding low elevations¶
mask.add_radial_sector(center_phi=0, sector_width=30, ... theta_max=60, degrees=True)
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | |
compute(mode='AND')
¶
Compute final combined boolean mask.
Parameters¶
mode : str Combination mode:
* ``'AND'`` – all constraints must be satisfied (intersection).
* ``'OR'`` – at least one constraint must be satisfied (union).
Returns¶
np.ndarray
Boolean array of shape (ncells,) where True = include.
Raises¶
ValueError If no masks have been added or mode is unknown.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | |
get_mask_summary()
¶
Get summary of all added masks.
Returns¶
dict Dictionary with mask names and per-mask / combined cell counts.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 | |
clear()
¶
Clear all masks.
Returns¶
SpatialMask Self for chaining.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
490 491 492 493 494 495 496 497 498 499 500 | |
__repr__()
¶
Return the developer-facing representation.
Returns¶
str Representation string.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
create_hemisphere_mask(grid, hemisphere='north')
¶
Create mask for a cardinal hemisphere.
Parameters¶
grid : GridData
Grid instance.
hemisphere : str
One of 'north', 'south', 'east', 'west'.
Returns¶
np.ndarray Boolean mask.
Raises¶
ValueError If hemisphere is not recognised.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | |
create_elevation_mask(grid, min_elevation=30.0, max_elevation=90.0)
¶
Create mask based on elevation angle.
Parameters¶
grid : GridData Grid instance. min_elevation : float Minimum elevation angle in degrees (default: 30°). max_elevation : float Maximum elevation angle in degrees (default: 90°).
Returns¶
np.ndarray Boolean mask.
Source code in packages/canvod-grids/src/canvod/grids/analysis/masking.py
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 | |
Weighting¶
Weighting strategies for spatial aggregation of hemispherical grid data.
Provides tools to calculate and combine different weighting schemes for computing weighted means across grid cells. Critical for unbiased spatial statistics when cells have different sizes or data quality.
Classes¶
WeightCalculator – builder for combined spatial weights.
Convenience functions¶
compute_uniform_weights – equal weight per cell.
compute_area_weights – solid-angle-only weights.
Notes¶
- Supported weight types:
solid_angle,observation_count,snr,sin_elevation,inverse_variance,custom. - Multiple weights are combined element-wise (multiply or add) and optionally normalised to sum to 1.
- Dask-backed datasets are handled efficiently: only scalar statistics are computed eagerly; masks stay lazy.
WeightCalculator
¶
Calculate and combine weights for spatial aggregation.
Parameters¶
grid : GridData
Grid instance.
ds : xr.Dataset or None
Dataset with data variables (required for data-dependent weights
such as observation_count, snr, inverse_variance).
Examples¶
weights = WeightCalculator(grid, vod_ds) weights.add_weight('solid_angle') weights.add_weight('observation_count', normalize=True) total_weights = weights.compute()
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 | |
__init__(grid, ds=None)
¶
Initialize the weight calculator.
Parameters¶
grid : GridData Grid instance. ds : xr.Dataset | None, optional Dataset with data variables for data-dependent weights.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
add_weight(weight_type, normalize=True, **kwargs)
¶
Add a weight component.
Parameters¶
weight_type : str
One of 'solid_angle', 'observation_count',
'snr', 'sin_elevation', 'inverse_variance',
'custom'.
normalize : bool
If True, normalise this component to sum to 1.0 before
combination.
**kwargs
Weight-specific parameters (see individual _compute_*
methods).
Returns¶
WeightCalculator Self for chaining.
Examples¶
calc.add_weight('solid_angle') calc.add_weight('observation_count', var_name='VOD', normalize=True) calc.add_weight('custom', values=my_weights, normalize=False)
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
compute(combination='multiply', normalize_final=True)
¶
Compute final combined weights.
Parameters¶
combination : str
'multiply' – element-wise product (default).
'add' – element-wise sum.
normalize_final : bool
If True, normalise the final array to sum to 1.0.
Returns¶
np.ndarray
Weight array of shape (ncells,).
Raises¶
ValueError If no weights have been added or combination is unknown.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
get_weight_summary()
¶
Summary statistics for each weight component.
Returns¶
dict Nested dict keyed by weight type.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
remove_weight(weight_type)
¶
Remove a weight component.
Parameters¶
weight_type : str Weight type to remove.
Returns¶
WeightCalculator Self for chaining.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | |
clear()
¶
Clear all weights.
Returns¶
WeightCalculator Self for chaining.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
241 242 243 244 245 246 247 248 249 250 251 252 | |
__repr__()
¶
Return the developer-facing representation.
Returns¶
str Representation string.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
254 255 256 257 258 259 260 261 262 263 264 265 266 | |
compute_uniform_weights(grid)
¶
Uniform weights (all cells equal).
Parameters¶
grid : GridData Grid instance.
Returns¶
np.ndarray
Array of 1 / ncells for each cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 | |
compute_area_weights(grid, normalize=True)
¶
Weights based on cell solid angles only.
Parameters¶
grid : GridData
Grid instance.
normalize : bool
If True, normalise to sum to 1.0.
Returns¶
np.ndarray Area-based weights.
Source code in packages/canvod-grids/src/canvod/grids/analysis/weighting.py
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 | |
Solar Geometry¶
Solar position calculations and corrections for VOD data.
Provides tools to compute solar positions and apply corrections to account for solar radiation effects on vegetation optical depth measurements.
Classes¶
SolarPositionCalculator – solar zenith / azimuth and VOD correction.
Convenience functions¶
compute_solar_zenith – quick zenith-only computation.
filter_daytime_data – mask nighttime observations.
Notes¶
- When pvlib is installed it is preferred for high-accuracy calculations. The built-in fallback uses NOAA algorithms (accuracy ~0.01° for 1800–2100).
- All public methods accept either
np.ndarrayofdatetime64orpd.DatetimeIndex.
SolarPositionCalculator
¶
Calculate solar positions for VOD corrections.
Parameters¶
lat : float
Observer latitude in degrees (positive = North).
lon : float
Observer longitude in degrees (positive = East).
elevation : float
Elevation above sea level in metres (default: 0).
use_pvlib : bool
If True, use pvlib when available (more accurate).
Falls back to built-in formulas automatically.
Examples¶
calc = SolarPositionCalculator(lat=40.0, lon=-105.0, elevation=1655) times = pd.date_range('2025-01-01', periods=24, freq='1H') zenith, azimuth = calc.compute_solar_position(times) corrected = calc.apply_solar_correction(vod_data, times)
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 | |
__init__(lat, lon, elevation=0.0, use_pvlib=True)
¶
Initialize the solar position calculator.
Parameters¶
lat : float Observer latitude in degrees. lon : float Observer longitude in degrees. elevation : float, default 0.0 Elevation above sea level in metres. use_pvlib : bool, default True Whether to use pvlib if available.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
compute_solar_position(times)
¶
Compute solar zenith and azimuth angles.
Parameters¶
times : np.ndarray or pd.DatetimeIndex
Array of datetime64 or DatetimeIndex.
Returns¶
solar_zenith : np.ndarray Solar zenith angles in degrees (0° = directly overhead). solar_azimuth : np.ndarray Solar azimuth angles in degrees (0° = North, 90° = East).
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
compute_solar_elevation(times)
¶
Compute solar elevation angle (complementary to zenith).
Parameters¶
times : np.ndarray or pd.DatetimeIndex Array of times.
Returns¶
np.ndarray Solar elevation angles in degrees (0° = horizon, 90° = overhead).
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | |
is_daytime(times, twilight_angle=-6.0)
¶
Determine if times are during daytime.
Parameters¶
times : np.ndarray or pd.DatetimeIndex
Array of times.
twilight_angle : float
Solar elevation threshold in degrees.
Common values: 0 (geometric), -6 (civil),
-12 (nautical), -18 (astronomical).
Returns¶
np.ndarray
Boolean array (True = daytime).
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | |
apply_solar_correction(data, method='normalize', reference_zenith=45.0)
¶
Apply solar correction to data.
Parameters¶
data : xr.DataArray
Input data with a time dimension ('epoch' or 'time').
method : str
Correction method:
* ``'normalize'`` – normalise by cos(zenith) relative to
*reference_zenith*.
* ``'residual'`` – subtract a 4th-order polynomial fitted
to the diurnal pattern (1-D data only; falls back to
``'normalize'`` for multi-dimensional data).
* ``'cos_correction'`` – simple cosine correction.
reference_zenith : float Reference zenith angle for normalisation (degrees).
Returns¶
xr.DataArray Solar-corrected data with correction metadata in attrs.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | |
compute_solar_bins(times, n_bins=12)
¶
Bin times by solar elevation angle.
Useful for solar-elevation-based composites instead of hour-of-day composites.
Parameters¶
times : np.ndarray or pd.DatetimeIndex Array of times. n_bins : int Number of solar elevation bins (range −20° to 90°).
Returns¶
np.ndarray Bin indices (0-based) for each time.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | |
get_sunrise_sunset(date)
¶
Compute sunrise and sunset times for a given date.
Parameters¶
date : datetime Date to compute sunrise/sunset for.
Returns¶
sunrise : pd.Timestamp or None
Sunrise time in UTC, or None if the sun never rises.
sunset : pd.Timestamp or None
Sunset time in UTC, or None if the sun never sets.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | |
__repr__()
¶
Return the developer-facing representation.
Returns¶
str Representation string.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
481 482 483 484 485 486 487 488 489 490 491 492 493 494 | |
compute_solar_zenith(lat, lon, times)
¶
Quick computation of solar zenith angles.
Parameters¶
lat : float Observer latitude in degrees. lon : float Observer longitude in degrees. times : np.ndarray or pd.DatetimeIndex Times to compute for.
Returns¶
np.ndarray Solar zenith angles in degrees.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
filter_daytime_data(data, lat, lon, twilight_angle=-6.0)
¶
Filter data to include only daytime observations.
Nighttime values are set to NaN.
Parameters¶
data : xr.DataArray
Data with a time dimension ('epoch' or 'time').
lat : float
Observer latitude in degrees.
lon : float
Observer longitude in degrees.
twilight_angle : float
Elevation threshold for daytime (degrees below horizon).
Returns¶
xr.DataArray Filtered data.
Source code in packages/canvod-grids/src/canvod/grids/analysis/solar.py
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 | |
Temporal Analysis¶
Temporal analysis of gridded VOD data.
Weighted time-series computation, diurnal cycle analysis, and temporal statistics with optional solar-position correction.
Classes¶
TemporalAnalysis
Main analysis class; binds a VOD dataset to a grid and exposes
methods for aggregation, solar correction, diurnal binning, and
basic plotting.
Notes¶
- All spatial masks and weight arrays must be 1-D with length
grid.ncells. - When a
SolarPositionCalculatoris attached (via site_lat / site_lon), additional solar-corrected and solar-binned methods become available. - Plotting helpers are thin wrappers around
matplotlib; they return(fig, ax)so callers can continue customising the figure.
TemporalAnalysis
¶
Temporal analysis of gridded VOD data.
Binds a VOD dataset (with pre-assigned cell IDs) to a grid and exposes weighted aggregation, diurnal analysis, and plotting.
Parameters¶
vod_ds : xr.Dataset
Dataset containing VOD data and a cell_id_<grid_name>
variable.
grid : GridData
Grid instance (must expose .ncells).
grid_name : str
Suffix for the cell-ID variable (e.g. 'htm_10deg').
site_lat : float or None, optional
Site latitude in degrees. Required for solar methods.
site_lon : float or None, optional
Site longitude in degrees. Required for solar methods.
site_elevation : float, optional
Site elevation in metres (default 0).
Raises¶
ValueError
If cell_id_<grid_name> is not present in vod_ds.
Examples¶
analysis = TemporalAnalysis(vod_ds, grid, "htm_10deg") ts = analysis.compute_timeseries(aggregate="1D")
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 | |
__init__(vod_ds, grid, grid_name, site_lat=None, site_lon=None, site_elevation=0.0)
¶
Initialize the temporal analysis helper.
Parameters¶
vod_ds : xr.Dataset VOD dataset containing cell IDs. grid : GridData Grid instance. grid_name : str Grid name suffix for cell IDs. site_lat : float | None, optional Site latitude in degrees. site_lon : float | None, optional Site longitude in degrees. site_elevation : float, default 0.0 Site elevation in metres.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
compute_timeseries(var_name='VOD', spatial_mask=None, weights=None, aggregate='1D', min_cells=1)
¶
Compute a weighted time-series aggregated over space.
Parameters¶
var_name : str, optional
Data variable to aggregate.
spatial_mask : np.ndarray or None, optional
Boolean mask of shape (grid.ncells,); True = include.
weights : np.ndarray or None, optional
Cell weights of shape (grid.ncells,); normalised
internally. None → uniform weights.
aggregate : str, optional
Pandas-compatible frequency string for temporal resampling.
min_cells : int, optional
Minimum unique cells required per time bin.
Returns¶
xr.Dataset
Variables: mean, std, n_cells,
n_observations, sum_weights.
Raises¶
ValueError If var_name is missing or mask/weight shapes are wrong.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | |
compute_timeseries_solar_corrected(var_name='VOD', spatial_mask=None, weights=None, aggregate='1D', min_cells=1, solar_correction='normalize', reference_zenith=45.0, daytime_only=False, twilight_angle=-6.0)
¶
Compute a time-series after applying a solar correction.
Parameters¶
var_name : str, optional
Data variable to correct and aggregate.
spatial_mask : np.ndarray or None, optional
Cell selection mask.
weights : np.ndarray or None, optional
Cell weights.
aggregate : str, optional
Temporal resampling frequency.
min_cells : int, optional
Minimum cells per time bin.
solar_correction : {'normalize', 'residual', 'cos_correction'}
Correction method passed to
:meth:SolarPositionCalculator.apply_solar_correction.
reference_zenith : float, optional
Reference zenith for normalisation (degrees).
daytime_only : bool, optional
If True, mask out nighttime epochs.
twilight_angle : float, optional
Solar-elevation threshold for daytime (degrees).
Returns¶
xr.Dataset Solar-corrected time-series with additional metadata attrs.
Raises¶
ValueError If no solar calculator is configured.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | |
compute_diurnal_cycle(var_name='VOD', spatial_mask=None, weights=None, hour_bins=24, min_observations=10)
¶
Compute clock-time diurnal cycle (hour-of-day statistics).
Parameters¶
var_name : str, optional Data variable to bin. spatial_mask : np.ndarray or None, optional Cell selection mask. weights : np.ndarray or None, optional Cell weights. hour_bins : int, optional Number of equal-width hour bins over [0, 24). min_observations : int, optional Minimum observations required per bin.
Returns¶
xr.Dataset
mean, std, n_observations on the hour
coordinate.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 | |
compute_diurnal_cycle_solar(var_name='VOD', spatial_mask=None, weights=None, n_solar_bins=12, min_observations=10)
¶
Diurnal cycle binned by solar elevation instead of clock time.
Accounts for seasonal variation in solar position, producing a more physically meaningful diurnal pattern.
Parameters¶
var_name : str, optional Data variable to bin. spatial_mask : np.ndarray or None, optional Cell selection mask. weights : np.ndarray or None, optional Cell weights. n_solar_bins : int, optional Number of equal-width bins over [-20°, 90°]. min_observations : int, optional Minimum observations per bin.
Returns¶
xr.Dataset
mean, std, n_observations on the
solar_elevation coordinate.
Raises¶
ValueError If no solar calculator is configured.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 | |
add_solar_metadata_to_timeseries(timeseries)
¶
Attach solar zenith, azimuth and elevation to a time-series.
Parameters¶
timeseries : xr.Dataset
Time-series dataset with an epoch coordinate.
Returns¶
xr.Dataset
Copy with solar_zenith, solar_azimuth,
solar_elevation added.
Raises¶
ValueError If no solar calculator is configured.
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 | |
plot_timeseries(timeseries, smooth_window=0, show_uncertainty=True, show_n_cells=False, ax=None, **style_kwargs)
¶
Plot a time-series with optional Savitzky-Golay smoothing.
Parameters¶
timeseries : xr.Dataset
Output of :meth:compute_timeseries.
smooth_window : int, optional
Savitzky-Golay window length (0 = off; forced odd internally).
show_uncertainty : bool, optional
Draw ±1 std band.
show_n_cells : bool, optional
Secondary y-axis showing cell count.
ax : plt.Axes or None, optional
Axes to draw on; created if None.
**style_kwargs
ylabel, title, figsize forwarded to matplotlib.
Returns¶
fig, ax : plt.Figure, plt.Axes
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 | |
plot_diurnal_cycle(diurnal, show_confidence=True, ax=None, **style_kwargs)
¶
Plot a clock-time diurnal cycle.
Parameters¶
diurnal : xr.Dataset
Output of :meth:compute_diurnal_cycle.
show_confidence : bool, optional
Draw ±1 std band.
ax : plt.Axes or None, optional
Axes to draw on; created if None.
**style_kwargs
ylabel, title, figsize.
Returns¶
fig, ax : plt.Figure, plt.Axes
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 | |
plot_diurnal_cycle_comparison(diurnal_clock, diurnal_solar, figsize=(14, 6), **style_kwargs)
¶
Side-by-side clock-time vs solar-time diurnal cycle plots.
Parameters¶
diurnal_clock : xr.Dataset
Output of :meth:compute_diurnal_cycle.
diurnal_solar : xr.Dataset
Output of :meth:compute_diurnal_cycle_solar.
figsize : tuple, optional
Figure size.
**style_kwargs
ylabel, title.
Returns¶
fig, axes : plt.Figure, np.ndarray of plt.Axes
Source code in packages/canvod-grids/src/canvod/grids/analysis/temporal.py
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 | |
Spatial Analysis¶
Spatial analysis of gridded VOD data.
Per-cell statistical aggregation and basic comparative plotting for VOD datasets with pre-assigned cell IDs.
Classes¶
VODSpatialAnalyzer
Computes per-cell temporal statistics and provides simple
histogram-based comparisons between filtering variants.
Notes¶
- Spatial visualisation (hemisphere maps, 3-D projections) is
handled by the
canvod-vizpackage. This module is limited to the statistical aggregation and lightweight comparison plots that do not require a hemisphere renderer. compute_spatial_statisticsreturns both a grid-aligned array (lengthgrid.ncells, NaN for empty cells) and a compact patch-aligned array containing only cells with observations. Use the grid-aligned form for masking / weighting; use the patch-aligned form when passing data tocanvod-vizrenderers.
VODSpatialAnalyzer
¶
Per-cell spatial analysis of a VOD dataset.
Parameters¶
vod_data : xr.Dataset
Dataset with a cell_id_<grid_name> variable already
assigned.
grid : GridData
Grid instance (must expose .ncells).
grid_name : str, optional
Suffix for the cell-ID variable.
Raises¶
ValueError If the expected cell-ID variable is missing.
Source code in packages/canvod-grids/src/canvod/grids/analysis/spatial.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
__init__(vod_data, grid, grid_name='equal_area_2deg')
¶
Initialize the spatial analyzer.
Parameters¶
vod_data : xr.Dataset Dataset with cell IDs. grid : GridData Grid instance. grid_name : str, default "equal_area_2deg" Grid name suffix for cell IDs.
Source code in packages/canvod-grids/src/canvod/grids/analysis/spatial.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
compute_spatial_statistics(var_name='VOD', time_agg='mean')
¶
Compute per-cell temporal statistics.
Parameters¶
var_name : str, optional Data variable to aggregate over time. time_agg : {'mean', 'std', 'count', 'median'} Aggregation function applied per cell.
Returns¶
dict Keys:
``grid_aligned`` : np.ndarray
Shape ``(grid.ncells,)``. NaN for cells without data.
Use for masking, weighting, or any grid-indexed operation.
``patch_aligned`` : np.ndarray
Compact array containing only cells with observations.
Use when passing data to ``canvod-viz`` renderers.
``cell_ids_with_data`` : np.ndarray
Integer cell IDs corresponding to ``patch_aligned``.
``metadata`` : dict
``valid_cells``, ``total_cells``, ``coverage_percent``,
``variable``, ``aggregation``.
Raises¶
ValueError If var_name is not in the dataset or time_agg is unrecognised.
Source code in packages/canvod-grids/src/canvod/grids/analysis/spatial.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
compare_filtering_methods(original_var='VOD', filtered_var='VOD_filtered', figsize=(16, 6), ax=None)
¶
Histogram comparison of original vs filtered VOD distributions.
Parameters¶
original_var : str, optional
Unfiltered variable name.
filtered_var : str, optional
Filtered variable name.
figsize : tuple, optional
Figure size when ax is None.
ax : tuple of two plt.Axes or None, optional
Pre-existing axes pair. Created if None.
Returns¶
fig, axes : plt.Figure, np.ndarray of plt.Axes
Source code in packages/canvod-grids/src/canvod/grids/analysis/spatial.py
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
Per-Cell VOD Analysis¶
Per-cell VOD analysis and plotting.
Statistical aggregation, diurnal dynamics, radial distributions, and theta-time heatmaps for per-cell VOD datasets. Handles single datasets or lists of datasets with configurable multi-dataset modes (separate vs averaged).
Classes¶
PerCellVODAnalyzer
Main analysis class. Accepts one or more per-cell datasets
(each must expose cell_timeseries, cell_theta,
cell_phi).
Utility functions¶
extract_percell_stats – temporal statistic per cell.
percell_to_grid_counts – total observation counts per cell.
extract_percell_temporal_stats – range / trend / CV per cell.
extract_percell_coverage – data-coverage percentage per cell.
percell_to_grid_data – thin wrapper around
:func:extract_percell_stats.
Notes¶
- Per-cell datasets are expected to have dimensions
(cell, time)and variablescell_timeseries,cell_theta,cell_phi, and optionallycell_weightsandcell_counts. - Spatial visualisation (hemisphere maps) lives in
canvod-viz.
PerCellVODAnalyzer
¶
Multi-dataset per-cell VOD analyzer.
Parameters¶
datasets : xr.Dataset or list of xr.Dataset
Per-cell dataset(s). Each must contain cell_timeseries,
cell_theta, and cell_phi.
labels : list of str or None, optional
Human-readable labels for each dataset.
Raises¶
ValueError If any dataset is missing required variables.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 | |
__init__(datasets, labels=None)
¶
Initialize the analyzer.
Parameters¶
datasets : xr.Dataset | list[xr.Dataset] Per-cell dataset(s). labels : list[str] | None, optional Labels for each dataset.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
extract_percell_stats(percell_ds, stat='median')
¶
Compute a single temporal statistic for every cell.
Parameters¶
percell_ds : xr.Dataset
Per-cell dataset with a cell_timeseries variable of shape
(cell, time).
stat : {"mean", "median", "std"}
Statistic to reduce across the time dimension.
Returns¶
np.ndarray
1-D array of length n_cells.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 | |
percell_to_grid_counts(percell_ds)
¶
Sum observation counts across time for each cell.
Parameters¶
percell_ds : xr.Dataset
Per-cell dataset with a cell_counts variable of shape
(cell, time).
Returns¶
np.ndarray 1-D array of total counts per cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
extract_percell_temporal_stats(percell_ds, stat='range')
¶
Compute a temporal-characteristic statistic for every cell.
Parameters¶
percell_ds : xr.Dataset
Per-cell dataset with cell_timeseries.
stat : {"range", "trend", "cv"}
Statistic:
* ``"range"`` – max − min over time.
* ``"trend"`` – linear-regression slope (requires ≥ 4 valid points;
otherwise NaN). Uses :mod:`scipy.stats`.
* ``"cv"`` – coefficient of variation (std / mean).
Returns¶
np.ndarray
1-D array of length n_cells.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 | |
extract_percell_coverage(percell_ds)
¶
Compute the fraction of valid (non-NaN) observations per cell.
Parameters¶
percell_ds : xr.Dataset
Per-cell dataset with cell_timeseries.
Returns¶
np.ndarray 1-D array of coverage percentages (0–100) per cell.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 | |
percell_to_grid_data(percell_ds, stat='median')
¶
Thin wrapper around :func:extract_percell_stats.
Provided for symmetry with aggregate_data_to_grid workflows.
Parameters¶
percell_ds : xr.Dataset Per-cell dataset. stat : {"mean", "median", "std"} Statistic to compute.
Returns¶
np.ndarray 1-D array compatible with grid visualisation.
Source code in packages/canvod-grids/src/canvod/grids/analysis/per_cell_analysis.py
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 | |
Analysis Storage¶
Persistent storage for precomputed analysis results via Icechunk.
Stores dataset+grid-specific analysis outputs in an Icechunk repository
under metadata/{dataset_name}/{grid_name}/:
- weights – per-cell weight arrays (
ncells,). - filter_masks – per-observation statistical masks (
epoch × sid). - spatial_masks – per-cell geometric selection masks (
ncells,). - statistics – per-cell aggregated statistics (
ncells,).
Depends on canvod-store at runtime. Install the package first::
uv add canvod-store
Classes¶
AnalysisStorage
Read / write / delete analysis metadata for a single Icechunk store.
AnalysisStorage
¶
Manage persistent storage of analysis results for dataset+grid pairs.
Storage layout inside the Icechunk repository::
metadata/{dataset_name}/{grid_name}/
├── weights/ # (ncells,)
│ ├── observation_count
│ ├── solid_angle
│ └── combined
├── filter_masks/ # (epoch, sid)
│ ├── mask_iqr
│ └── mask_zscore
├── spatial_masks/ # (ncells,)
│ ├── mask_north
│ └── mask_high_elevation
└── statistics/ # (ncells,)
├── obs_count
├── mean_vod
└── std_vod
Parameters¶
store_path : Path or str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 | |
__init__(store_path)
¶
Initialize the storage manager.
Parameters¶
store_path : Path | str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
77 78 79 80 81 82 83 84 85 86 87 | |
__repr__()
¶
Return the developer-facing representation.
Returns¶
str Representation string.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
89 90 91 92 93 94 95 96 97 98 | |
store_weights(dataset_name, grid_name, weights, weight_params=None, overwrite=False)
¶
Store per-cell weight arrays.
Parameters¶
dataset_name : str
Dataset identifier (e.g. 'reference_01_canopy_01').
grid_name : str
Grid identifier (e.g. 'equal_area_2deg').
weights : dict
{name: array} – all arrays must have shape (ncells,).
weight_params : dict, optional
Parameters used to compute each weight type.
overwrite : bool
Overwrite existing weights.
Returns¶
str Icechunk snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
load_weights(dataset_name, grid_name, weight_type=None)
¶
Load stored weight arrays.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
weight_type : str, optional
Load only this weight. None loads all.
Returns¶
dict
{name: ndarray} of loaded weights.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | |
has_weights(dataset_name, grid_name)
¶
Return True if weights exist for the dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
220 221 222 223 224 225 226 | |
store_filter_masks(dataset_name, grid_name, masks, filter_params=None, overwrite=False)
¶
Store per-observation filter masks at native resolution.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
masks : dict
{filter_name: DataArray} – all must share the same
(epoch, sid) shape.
filter_params : dict, optional
Parameters used for each filter.
overwrite : bool
Overwrite existing masks.
Returns¶
str Icechunk snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
load_filter_mask(dataset_name, grid_name, filter_type, attach_coords=True)
¶
Load a single filter mask.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
filter_type : str
Filter name (e.g. 'iqr', 'zscore').
attach_coords : bool
Re-attach epoch / sid coordinates from the source
dataset group. Set False for faster loading when
coordinates are not needed.
Returns¶
xr.DataArray
Boolean mask with shape (epoch, sid).
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
load_all_filter_masks(dataset_name, grid_name)
¶
Load all stored filter masks.
Returns¶
dict
{filter_name: DataArray} – boolean masks.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |
has_filter_masks(dataset_name, grid_name)
¶
Return True if filter masks exist for the dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
416 417 418 419 420 421 422 | |
store_spatial_masks(dataset_name, grid_name, masks, mask_descriptions=None, overwrite=False)
¶
Store per-cell geometric selection masks.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
masks : dict
{name: bool_array} – all arrays must have shape (ncells,)
and boolean dtype.
mask_descriptions : dict, optional
Human-readable description for each mask (stored as variable attrs).
overwrite : bool
Overwrite existing masks.
Returns¶
str Icechunk snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | |
load_spatial_mask(dataset_name, grid_name, mask_name)
¶
Load a single spatial mask.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
mask_name : str
Mask name (e.g. 'north', 'high_elevation').
Returns¶
np.ndarray
Boolean array with shape (ncells,).
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 | |
load_all_spatial_masks(dataset_name, grid_name)
¶
Load all spatial masks.
Returns¶
dict
{name: bool_ndarray}.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | |
has_spatial_masks(dataset_name, grid_name)
¶
Return True if spatial masks exist for the dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
582 583 584 585 586 587 588 | |
store_statistics(dataset_name, grid_name, stats, overwrite=False)
¶
Store pre-computed per-cell statistics.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
stats : dict
{name: array} – all arrays must have shape (ncells,).
Variables whose name ends with '_count' or equals
'obs_count' are stored as int64; everything else as
float32.
overwrite : bool
Overwrite existing statistics.
Returns¶
str Icechunk snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 | |
load_statistics(dataset_name, grid_name, stat_name=None)
¶
Load pre-computed per-cell statistics.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
stat_name : str, optional
Load only this statistic. None loads all.
Returns¶
dict
{name: ndarray}.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 | |
has_statistics(dataset_name, grid_name)
¶
Return True if statistics exist for the dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
725 726 727 728 729 730 731 | |
list_available_metadata(dataset_name, grid_name)
¶
Check which metadata categories are stored.
Returns¶
dict
{category: bool} for weights, filter_masks, spatial_masks,
statistics.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 | |
get_metadata_summary(dataset_name, grid_name)
¶
Detailed summary of all stored metadata for a dataset+grid pair.
Returns¶
dict Nested summary with availability flags and per-category details.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 | |
delete_weights(dataset_name, grid_name)
¶
Delete all weights for a dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
839 840 841 842 843 844 | |
delete_filter_masks(dataset_name, grid_name)
¶
Delete all filter masks for a dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
846 847 848 849 850 851 | |
delete_spatial_masks(dataset_name, grid_name)
¶
Delete all spatial masks for a dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
853 854 855 856 857 858 | |
delete_statistics(dataset_name, grid_name)
¶
Delete all statistics for a dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
860 861 862 863 864 865 | |
delete_all_metadata(dataset_name, grid_name)
¶
Delete the entire metadata subtree for a dataset+grid pair.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
867 868 869 870 871 872 | |
delete_specific_weight(dataset_name, grid_name, weight_name)
¶
Delete a single weight variable from an existing weights group.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
weight_name : str
Weight variable name (e.g. 'observation_count').
Returns¶
str Snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | |
delete_specific_filter_mask(dataset_name, grid_name, filter_type)
¶
Delete a single filter mask variable from an existing filter_masks group.
Parameters¶
dataset_name : str
Dataset identifier.
grid_name : str
Grid identifier.
filter_type : str
Filter type name (e.g. 'iqr').
Returns¶
str Snapshot ID.
Source code in packages/canvod-grids/src/canvod/grids/analysis/analysis_storage.py
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 | |
Workflows¶
Core adapted VOD workflow.
Orchestrates the full VOD analysis pipeline: loading data from an
Icechunk store, applying cell-SID Hampel filtering (vectorised or
parallelised), and persisting results back to a processing branch.
Classes¶
AdaptedVODWorkflow
Main workflow entry-point. Lazily imports canvod-store so the
package can be imported even when the store is not installed.
Functions¶
get_workflow_for_store
Convenience factory.
check_processed_data_status
Introspect the store for previously filtered data.
AdaptedVODWorkflow
¶
Core VOD analysis workflow with polars-optimised loading and refined temporal matching.
All heavy lifting (filtering, grid operations) is delegated to
canvod.grids.analysis. This class is responsible only for
Icechunk I/O and orchestration.
Parameters¶
vod_store_path : Path or str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 | |
__init__(vod_store_path)
¶
Initialize the workflow.
Parameters¶
vod_store_path : Path | str Path to the VOD Icechunk store directory.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
96 97 98 99 100 101 102 103 104 105 106 | |
load_vod_data(group_name='reference_01_canopy_01', branch='main')
¶
Load a VOD dataset from the store.
Parameters¶
group_name : str Zarr group path inside the store. branch : str Icechunk branch to read from.
Returns¶
xr.Dataset Lazy-loaded VOD dataset.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
check_temporal_coverage_compatibility(main_ds, processed_ds, requested_time_range=None)
¶
Check whether processed_ds adequately covers a time range.
When requested_time_range is None the method checks that the
processed dataset covers at least 70 % of the main dataset's span.
When a range is given it verifies that both endpoints fall within the
processed dataset (with a 1-day tolerance).
Parameters¶
main_ds : xr.Dataset
Reference (unfiltered) dataset.
processed_ds : xr.Dataset
Filtered dataset to validate.
requested_time_range : tuple of date, optional
(start, end) to check against.
Returns¶
compatible : bool
coverage_info : dict
Diagnostic information with main_range, processed_range,
and requested_range.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
create_processed_data_fast_hampel_complete(start_date, end_date, force_recreate=False, window_hours=1.0, sigma_threshold=3.0, min_points=5, ultra_fast_mode=False, cell_batch_size=200, n_workers=None)
¶
Run the vectorised / ultra-fast Hampel pipeline end-to-end.
Delegates the actual filtering to
:func:canvod.grids.analysis.sigma_clip_filter.astropy_hampel_vectorized_fast
(or its ultra-fast variant) and persists the result on a
processing branch.
Parameters¶
start_date, end_date : date or datetime
Temporal extent to process.
force_recreate : bool
Overwrite existing filtered data.
window_hours : float
Hampel temporal window in hours.
sigma_threshold : float
MAD-based outlier threshold.
min_points : int
Minimum observations required per window.
ultra_fast_mode : bool
Use the pure-NumPy sigma-clip path (faster, less precise).
cell_batch_size : int
Number of cells per spatial batch.
n_workers : int, optional
Parallel workers. None → auto-detect.
Returns¶
str or None
Icechunk snapshot ID, or None if existing data was kept.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | |
create_processed_data_hampel_parallel_complete(start_date, end_date, force_recreate=False, threshold=3.0, min_obs_per_sid=20, spatial_batch_size=500, n_workers=None, temporal_agg=None, agg_method=None)
¶
Run the parallelised cell-SID Hampel pipeline end-to-end.
Loads the complete requested time range (no temporal chunking) and
applies
:func:canvod.grids.analysis.hampel_filtering.aggr_hampel_cell_sid_parallelized
with spatial batching.
Parameters¶
start_date, end_date : date or datetime
Temporal extent to process.
force_recreate : bool
Overwrite existing filtered data.
threshold : float
MAD-based outlier threshold.
min_obs_per_sid : int
Minimum observations per cell-SID combination.
spatial_batch_size : int
Cells per spatial batch.
n_workers : int, optional
Parallel workers. None → auto-detect.
temporal_agg : str, optional
Post-filtering aggregation frequency (e.g. '1H', '1D').
agg_method : str, optional
Aggregation method (e.g. 'mean').
Returns¶
str or None
Icechunk snapshot ID, or None if existing data was kept.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
run_complete_workflow(group_name='reference_01_canopy_01', branch='auto', time_range=None, **kwargs)
¶
Orchestrate a complete analysis run.
Auto-detection logic (branch='auto') looks for Hampel-filtered
data on the processing branch first. If found and temporally
compatible it is used directly; otherwise raw data from main is
returned.
Parameters¶
group_name : str
Zarr group for the raw VOD data.
branch : str
'auto' for detection, or an explicit branch name.
time_range : tuple of date, optional
(start, end) to select.
Returns¶
dict
Keys: final_data (Dataset), source_branch,
pre_filtered (bool), filter_type.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | |
normalize_datetime_for_comparison(dt)
¶
Coerce date to datetime at midnight for safe comparison.
Parameters¶
dt : datetime.date | datetime.datetime Date-like input.
Returns¶
datetime.datetime Datetime at midnight.
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
get_workflow_for_store(vod_store_path)
¶
Create an :class:AdaptedVODWorkflow for the given store path.
Parameters¶
vod_store_path : Path or str Path to VOD Icechunk store.
Returns¶
AdaptedVODWorkflow
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
703 704 705 706 707 708 709 710 711 712 713 714 715 716 | |
check_processed_data_status(vod_store_path)
¶
Introspect the store for Hampel-filtered data.
Parameters¶
vod_store_path : Path or str Path to VOD Icechunk store.
Returns¶
dict
Keys: has_processing_branch, has_hampel_data,
temporal_coverage (tuple of dates or None),
data_size (dict or None).
Source code in packages/canvod-grids/src/canvod/grids/workflows/adapted_workflow.py
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | |